/*
 * ATNF Javascript library
 * Copyright (C) 2012 Jamie Stevens
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

/**
 * @fileoverview This library contains useful objects for interacting
 * with the ATNF telescopes (usually through MoniCA), and more general
 * astronomical tasks such as coordinate conversion and handling of time.
 * @author ste616@gmail.com (Jamie Stevens)
 */

// Our Dojo requirements are listed here.
dojo.require('dojo.cookie');
dojo.require('dojo.date');
dojo.require('dojo.number');
dojo.require('dojox.timing._base');
dojo.require('dojo.io.script');

/**
 * This function handles all communications for the ATNF online observing
 * log database.
 * @param {object} spec Specifications for the object setup.
 * @param {object} my   An object reference that this object should be able
 *                      to access.
 */
var atnfOnlineLogConnection = function(spec, my) {
	/**
	 * The object we return to our callers.
	 * @type {object}
	 */
	var that = {};
	
	// Set some sensible defaults in the spec object.
	spec = spec || {};

	/**
	 * The script to call.
	 * @type {string}
	 */
	spec.serverScript = spec.serverScript || 'cgi-bin/logactions.pl';
	
	/**
	 * The protocol to use for the remote call.
	 * @type {string}
	 */
	spec.protocol = spec.protocol || location.protocol;
	
	/**
	 * The server where the script is located.
	 * @type {string}
	 */
	spec.server = spec.server || location.hostname;

	// Our methods follow.

	// Our private methods.
	/**
	 * Call the server to get/put some information.
	 * @param {object} options Controls the server query.
	 */
	var comms = function(options) {
		// Setup the connection deferred.
		var postDeferred = dojo.xhrPost({
			url: spec.protocol + '//' + spec.server + '/' +
					spec.serverScript,
			sync: false,
			content: options.content,
			handleAs: 'json'
		});
		
		// Return the deferred promise.
		return postDeferred;
	};

	// Our public methods.
	/**
	 * The public access to the comms routine.
	 * @param {object} pOptions Controls the server query.
	 */
	that.comms = function(pOptions) {
		return comms(pOptions);
	};
	
	return that;
};

/**
 * This function authenticates a user against the ATNF online observing
 * log database list of users.
 * @param {object} spec Specifications for the object setup.
 * @param {object} my   An object reference that this object should be able
 *                      to access.
 */
var atnfOnlineLogAuthenticate = function(spec, my) {
	/**
	 * The object we return to our callers.
	 * @type {object}
	 */
	var that = {};
	
	// Set some sensible defaults in the spec object.
	spec = spec || {};

	/**
	 * The connection to use.
	 * @type {string}
	 */
	spec.connection = spec.connection || null;
	
	/**
	 * The name of the session ID cookie to use.
	 * @type {string}
	 */
	spec.sessionCookie = spec.sessionCookie || 'atnfOnlineLogSessionId';
	
	/**
	 * The username to authenticate with.
	 * @type {string}
	 */
	var userName = '';
	
	/**
	 * The password to authenticate with.
	 * @type {string}
	 */
	var password = '';
	
	/**
	 * Our session ID, if we've successfully authenticated.
	 * @type {string}
	 */
	var sessionId = '';

	/**
	 * The deferred promise we make while authenticating.
	 * @type {Deferred}
	 */
	var authenticationPromise = null;

	/**
	 * The last error message we got from a server call.
	 * @type {string}
	 */
	var lastError = '';
	
	// Our methods follow.

	// Our private methods.

	/**
	 * Hash our password.
	 * @param {string} rawPass The unhashed password.
	 */
	var passwordHash = function(rawPass) {
		// We use the hex_md5 JS library function.
		return hex_md5(rawPass);
	};

	/**
	 * Make a random session ID.
	 */
	var makeSessionId = function() {
		return Math.random().toString().substring(2);
	};
	
	/**
	 * Recall a saved session ID.
	 */
	var recallSessionId = function() {
		// Check for a cookie.
		if (dojo.cookie(spec.sessionCookie)) {
			// Use this.
			return dojo.cookie(spec.sessionCookie);
		}
		
		return '';
	};

	/**
	 * Delete a saved session ID.
	 */
	var deleteSessionId = function() {
		dojo.cookie(spec.sessionCookie, null, { expires: -1 });
	};
	
	/**
	 * Save the session ID as a cookie.
	 */
	var saveSessionId = function() {
		if (sessionId !== '') {
			var cOptions = {
				expires: 5
			};
			dojo.cookie(spec.sessionCookie, sessionId, cOptions);
		}
	};
	
	/**
	 * Try to authenticate using our current ID.
	 */
	var authenticate = function() {
		// Set up our authentication promise.
		if (authenticationPromise === null) {
			authenticationPromise = new dojo.Deferred();
		}

		// Check for an existing session ID.
		var oSessionId = recallSessionId();
		if (oSessionId !== '') {
			// Try to authenticate against a saved session ID.
			spec.connection.comms({
				content: {
					action: 'check_authorisation',
					sessionid: oSessionId
				}
			}).then(function(data, ioargs) {
				// Keep track if we fail.
				var haveFailed = false;
				if (typeof data === 'undefined' ||
						typeof data.authorised === 'undefined' ||
						data.authorised === '') {
					haveFailed = true;
				} else {
					// We have successfully authenticated.
					userName = data.authorised;
					sessionId = oSessionId;
					authenticationPromise.resolve(that);
				}
				// If we have failed we clear the session ID cookie and call the
				// authentication routine again.
				if (haveFailed === true) {
					deleteSessionId();
					authenticate();
				}
			});
		} else {
			if (userName === '' || password === '') {
				// We have no information.
				return null;
			}
		
			// Make a new session ID.
			oSessionId = makeSessionId();
			// Try to authenticate against the supplied username and password.
			spec.connection.comms({
				content: {
					action: 'login',
					user_email: userName,
					user_hash: password,
					sessionid: oSessionId
				}
			}).then(function(data, ioargs) {
				// Keep track if we fail.
				var haveFailed = false;
				if (typeof data === 'undefined' ||
						typeof data.success === 'undefined' ||
						data.success !== true) {
					haveFailed = true;
					if (dojo.isString(data.error) === true) {
						lastError = data.error;
					}
				} else {
					// We have successfully authenticated.
					sessionId = oSessionId;
					saveSessionId();
					authenticationPromise.resolve(that);
				}
				// If we have failed, we reject the promise.
				if (haveFailed === true) {
					authenticationPromise.reject(that);
				}
			});
		}
		
		// Return our promise.
		return authenticationPromise;
	};

	/**
	 * Tell the server we want to logout.
	 */
	var deauthenticate = function() {
		// We can only deauthenticate if we have a session ID.
		if (sessionId === '') {
			return null;
		}

		// Set up our authentication promise.
		if (authenticationPromise === null) {
			authenticationPromise = new dojo.Deferred();
		}

		spec.connection.comms({
			content: {
				action: 'logout',
				sessionid: sessionId
			}
		}).then(function(data, ioargs) {
			// Keep track if we fail.
			var haveFailed = false;
			if (typeof data === 'undefined' ||
					typeof data.success === 'undefined' ||
					data.success !== true) {
				haveFailed = true;
				if (dojo.isString(data.error) === true) {
					lastError = data.error;
				}
			} else {
				// We have successfully deauthenticated.
				sessionId = '';
				deleteSessionId();
				authenticationPromise.resolve(that);
			}
			// If we have failed, we reject the promise.
			if (haveFailed === true) {
				authenticationPromise.reject(that);
			}
		});
		
		return authenticationPromise;
	};
	
	// Our public methods.
	/**
	 * The user wants to set the username and password used to authenticate.
	 * @param {string} aUserName The username to use.
	 * @param {string} aPassword The password to use.
	 */
	that.setUserPass = function(aUserName, aPassword) {
		if (dojo.isString(aUserName) === true) {
			userName = aUserName;
		}
		if (dojo.isString(aPassword) === true) {
			password = passwordHash(aPassword);
		}
		
		// Return ourselves for method chaining.
		return that;
	};

	/**
	 * Start the authentication process.
	 */
	that.authenticate = function() {
		// Start a new authentication promise.
		authenticationPromise = null;
		
		// Use our private method for all the heavy lifting.
		return authenticate();
	};

	/**
	 * Start the deauthentication process.
	 */
	that.deauthenticate = function() {
		// Start a new deauthentication promise.
		authenticationPromise = null;
		
		return deauthenticate();
	};
	
	/**
	 * Return the last error string.
	 */
	that.errorString = function() {
		return lastError;
	};

	/**
	 * Return the session ID for the authenticated session.
	 */
	that.sessionId = function() {
		return sessionId;
	};

	/**
	 * Return the user that has authenticated.
	 */
	that.user = function() {
		return userName;
	};
	
	// Check that we have a comms routine.
	if (spec.connection !== null &&
			dojo.isFunction(spec.connection.comms) === true) {
		return that;
	} else {
		return null;
	}
	
};


/**
 * This function makes it possible to access and query the online log
 * database. It handles all the server side querying, and can create
 * log objects for any particular log that the user wants to access.
 * @param {object} spec Specifications for the object setup.
 * @param {object} my   An object reference that this object should be able
 *                      to access.
 */
var atnfOnlineLogDatabase = function(spec, my) {
	/**
	 * The object we return to our callers.
	 * @type {object}
	 */
	var that = {};
	
	// Set some sensible defaults in the spec object.
	spec = spec || {};

	/**
	 * The connection to use.
	 * @type {atnfOnlineLogConnection}
	 */
	spec.connection = spec.connection || null;
	
	/**
	 * A persistent session ID.
	 * @type {string}
	 */
	var sessionId;
	
	// Our methods follow.

	// Our private methods.

	// Our public methods.
	/**
	 * Load a log with the specified log ID.
	 * @param {number} logId The ID of the log to load.
	 * @param {string} sId An optional session ID string.
	 */
	that.loadLog = function(logId, sId) {
		// Check that we have a log ID.
		if (typeof logId === 'undefined') {
			return null;
		}
		
		var llPromise = new dojo.Deferred();

		sId = sId || sessionId;
		
		spec.connection.comms({
			content: {
				action: 'get_log_info',
				sessionid: sId,
				logid: logId
			}
		}).then(function(data, ioargs) {
			if (typeof data === 'undefined' ||
					typeof data.name === 'undefined') {
				// Failed.
				llPromise.reject();
			}
							
			// Make a new log.
			atnfOnlineLog({
				connection: spec.connection,
				database: that,
				sessionId: sId,
				databaseEntry: data
			}).then(function(logHandler) {
				llPromise.resolve(logHandler);
			});
		});
		
		return llPromise;
	};

	/**
	 * Get a list of all the accessible logs.
	 * @param {string} sId An optional session ID string.
	 */
	that.accessibleLogs = function(sId) {
		var alPromise = new dojo.Deferred();

		sId = sId || sessionId;
		
		spec.connection.comms({
			content: {
				action: 'available_logs',
				sessionid: sId
			}
		}).then(function(data, ioargs) {
			// Check we get something sensible.
			if (typeof data === 'undefined' ||
					typeof data.available === 'undefined') {
				// This is a failure.
				alPromise.reject();
			} else {
				// Return the data as is.
				alPromise.resolve(data);
			}
		});
		
		return alPromise;
	};

	/**
	 * Set the session ID.
	 * @param {string} sId The session ID string.
	 */
	that.setSessionId = function(sId) {
		if (dojo.isString(sId)) {
			sessionId = sId;
		}
		return that;
	};
	
	// Check that we have a comms routine.
	if (spec.connection !== null &&
			dojo.isFunction(spec.connection.comms) === true) {
		return that;
	} else {
		return null;
	}
};

/**
 * This function represents a single online log.
 * @param {object} spec Specifications for the object setup.
 * @param {object} my   An object reference that this object should be able
 *                      to access.
 */
var atnfOnlineLog = function(spec, my) {
	/**
	 * The object we return to our callers.
	 * @type {object}
	 */
	var that = {};
	
	// Set some sensible defaults in the spec object.
	spec = spec || {};

	/**
	 * The connection to use.
	 * @type {atnfOnlineLogConnection}
	 */
	spec.connection = spec.connection || null;

	/**
	 * The log database handler to use.
	 * @type {atnfOnlineLogDatabase}
	 */
	spec.database = spec.database || null;

	/**
	 * The session ID we were authenticated with.
	 * @type {string}
	 */
	spec.sessionId = spec.sessionId || '';
	
	/**
	 * An object describing our database entry.
	 * @type {object}
	 */
	spec.databaseEntry = spec.databaseEntry || {};
	
	/**
	 * The promise we make when we are made, that will be resolved when
	 * the log is ready for use.
	 * @type {Deferred}
	 */
	var loadingPromise;
	
	/**
	 * The log entries we have.
	 * @type {array}
	 */
	var logEntries = [];
	
	// Our methods follow.

	// Our private methods.
	/**
	 * Start the log initialisation.
	 */
	var initialise = function() {
		// Make our loading promise.
		loadingPromise = new dojo.Deferred();
		
		// Check for any existing entries in this log.
		spec.connection.comms({
			content: {
				action: 'get_log_entries',
				sessionid: spec.sessionId,
				logid: spec.databaseEntry['log_id']
			}
		}).then(function(data, ioargs) {
			if (typeof data === 'undefined' ||
					typeof data.entries === 'undefined') {
				// Failure.
				loadingPromise.reject();
				return;
			}
			// Make a new log entry for each returned entry.
			for (var i = 0; i < data.entries.length; i++) {
				addEntry(data.entries[i]);
			}
			// We have now completed loading.
			loadingPromise.resolve(that);
		});
		
		return loadingPromise;
	};

	/**
	 * Add a new entry to the log.
	 * @param {object} initData An object describing the log contents.
	 */
	var addEntry = function(initData) {
		// Add the necessary parameters to the data.
		var addData = {
			content: initData,
			log: that,
			connection: spec.connection,
			sessionid: spec.sessionId
		};
		
		var newEntry = atnfOnlineLogEntry(addData);
		if (newEntry !== null) {
			logEntries.push(newEntry);
		}
	};
	
	/**
	 * Get some entries.
	 * @param {number} entryNum The entry number to retrieve, or -1 for all.
	 *                          This can also be specified as an array of all
	 *                          entries to retrieve.
	 */
	var getEntries = function(entryNum) {
		var rEntries = [];

		if (dojo.isArray(entryNum) === true) {
			for (var i = 0; i < entryNum.length; i++) {
				rEntries[i] = (entryNum[i] < logEntries.length) ?
					logEntries[entryNum[i]] : null;
			}
		} else if (entryNum === -1) {
			// Send back a copy of the entire array.
			rEntries = logEntries.slice();
		} else {
			rEntries[0] = (entryNum < logEntries.length) ?
				logEntries[entryNum] : null;
		}
		
		return rEntries;
	};
	
	// Our public methods.
	/**
	 * Get all our entries.
	 */
	that.getAllEntries = function() {
		return getEntries(-1);
	};

	
	// Check that we have a comms and database routine.
	if (spec.connection !== null &&
			dojo.isFunction(spec.connection.comms) === true &&
			spec.database !== null &&
			dojo.isFunction(spec.database.setSessionId) === true) {
		// Initialise ourselves.
		return initialise();
	} else {
		return null;
	}
};

/**
 * This function represents a single online log entry.
 * @param {object} spec Specifications for the object setup.
 * @param {object} my   An object reference that this object should be able
 *                      to access.
 */
var atnfOnlineLogEntry = function(spec, my) {
	/**
	 * The object we return to our callers.
	 * @type {object}
	 */
	var that = {};
	
	// Set some sensible defaults in the spec object.
	spec = spec || {};

	/**
	 * The connection to use.
	 * @type {atnfOnlineLogConnection}
	 */
	spec.connection = spec.connection || null;

	/**
	 * The log handler to use.
	 * @type {atnfOnlineLogDatabase}
	 */
	spec.log = spec.log || null;

	/**
	 * The session ID we were authenticated with.
	 * @type {string}
	 */
	spec.sessionId = spec.sessionId || '';
	
	/**
	 * An object containing the content of the entry.
	 * @type {object}
	 */
	spec.content = spec.content || {};
	
	// Our methods follow.

	// Our private methods.
	/**
	 * Initialise the entry.
	 */
	var initialise = function() {
		// Check that we have all the information we need.
		if (typeof spec.content.entryid === 'undefined' ||
				typeof spec.content.logid === 'undefined' ||
				typeof spec.content.entrytime === 'undefined' ||
				typeof spec.content.content === 'undefined' ||
				typeof spec.content.alteredtime === 'undefined' ||
				typeof spec.content.type === 'undefined' ||
				typeof spec.content.editor === 'undefined') {
			// Something is missing, so we can't be created.
			return false;
		}
		
		// We have all our information, so we check whether we have the right
		// connections.
		if (spec.connection === null ||
				dojo.isFunction(spec.connection.comms) === false ||
				spec.log === null ||
				dojo.isFunction(spec.log.getAllEntries) === false) {
			// The connections weren't specified, so we can't be created.
			return false;
		}
		
		// We have everything we need.
		return true;
	};
	
	// Our public methods.
	/**
	 * Return all the content of our entry.
	 */
	that.getContent = function() {
		return dojo.clone(spec.content);
	};
	
	// Check that we have all the information we need.
	if (initialise() === true) {
		return that;
	} else {
		return null;
	}
};


/**
 * This function is the basis for automated monitoring of data points
 * relating to a particular telescope component, although the component
 * can be arbitrarily defined. The user asks this object to monitor a
 * set of MoniCA points, and monitor them regularly for changes, and/or
 * to trigger events if the points meet user-defined criteria.
 * @param {object} spec Specifications for the object setup.
 * @param {object} my   An object reference that this object should be able
 *                      to access.
 */
var telescopeComponent = function(spec, my) {
	/**
	 * The object we return to our caller.
	 * @type {object}
	 */
	var that = {};
	
	// Set some sensible defaults in the spec object.
	spec = spec || {};
	
	/**
	 * The MoniCA handler object.
	 * @type {object}
	 */
	spec.monica = spec.monica || null;
	
	/**
	 * The points related to this telescope component.
	 * @type {array}
	 */
	var relatedPoints = [];
	
	/**
	 * A list of error conditions that we should check for.
	 * @type {array}
	 */
	var errorConditions = [];
	
	/**
	 * The condition we use to decide when to evaluate this component's status.
	 * @type {object}
	 */
	var checkCondition = {};
	
	/**
	 * The function to call every time the component is evaluated.
	 * @type {function}
	 */
	var checkCall = null;
	
	/**
	 * The function to call when the component is evaluated to be in error.
	 * @type {function}
	 */
	var errorCall = null;

	/**
	 * A timer required for periodic evaluation.
	 * @type {Timer}
	 */
	var evaluateTimer = new dojox.timing.Timer();

	/**
	 * The known types of error checks.
	 * @type {array}
	 */
	var knownErrorActions = [ 'valueAbove', 'valueBelow',
														'valueRange', 'errorState' ];

	/**
	 * The usable default error check routines.
	 * @type {array}
	 */
	var usableErrorRoutines = {};
	
	// Our methods follow.

	// Our private methods.
	/**
	 * Check that we have a MoniCA connection.
	 */
	var hasMoniCA = function() {
		if (spec.monica !== null) {
			return true;
		} else {
			return false;
		}
	};

	/**
	 * Set a default logic if required for the error checkers.
	 * @param {array} cArray The conditions to check.
	 * @param {string} logic The way to handle multiple checks.
	 */
	var defaultLogic = function(cArray, logic) {
		if (cArray.length === 1) {
			// Only a single point.
			logic = 'or';
		} else {
			// Set a default for the logic.
			logic = logic || 'and';
		}
		
		return logic;
	};
	
	/**
	 * Evaluate a logic combination.
	 * @param {array} conditions A set of Booleans indicating the state of
	 *                           several conditions.
	 * @param {string} logic The way to handle the Boolean combination.
	 */
	var evaluateLogic = function(conditions, logic) {
		var endCondition = conditions[0];
		for (var l = 1; l < conditions.length; l++) {
			if (logic === 'and') {
				endCondition = endCondition && conditions[l];
			} else if (logic === 'or') {
				endCondition = endCondition || conditions[l];
			}
		}
		
		return endCondition;
	};
	
	/**
	 * Error checking method: check if a number of point values
	 * are larger than some reference values.
	 * @param {array} pointNames The names of the points to check.
	 * @param {array} refValues The comparison values.
	 * @param {string} logic The way to handle multiple checks.
	 */
	usableErrorRoutines.valueAbove = function(pointNames, refValues, logic) {
		logic = defaultLogic(pointNames, logic);
		
		// Go through the points.
		var conditions = [];
		for (var p = 0; p < pointNames.length; p++) {
			// Get the latest value for this point.
			var pVal = findPointReference(pointNames[p]).latestValue().value;
			if (pVal > refValues[p]) {
				conditions[p] = true;
			} else {
				conditions[p] = false;
			}
		}

		return evaluateLogic(conditions, logic);
	};

	/**
	 * Error checking method: check if a number of point values
	 * are smaller than some reference values.
	 * @param {array} pointNames The names of the points to check.
	 * @param {array} refValues The comparison values.
	 * @param {string} logic The way to handle multiple checks.
	 */
	usableErrorRoutines.valueBelow = function(pointNames, refValues, logic) {
		logic = defaultLogic(pointNames, logic);
		
		// Go through the points.
		var conditions = [];
		for (var p = 0; p < pointNames.length; p++) {
			// Get the latest value for this point.
			var pVal = findPointReference(pointNames[p]).latestValue().value;
			if (pVal < refValues[p]) {
				conditions[p] = true;
			} else {
				conditions[p] = false;
			}
		}

		return evaluateLogic(conditions, logic);
	};

	/**
	 * Error checking method: check if a number of point values
	 * are within than some reference ranges.
	 * @param {array} pointNames The names of the points to check.
	 * @param {array} refValues The comparison values.
	 * @param {string} logic The way to handle multiple checks.
	 */
	usableErrorRoutines.valueRange = function(pointNames, refValues, logic) {
		logic = defaultLogic(pointNames, logic);
		
		// Go through the points.
		var conditions = [];
		for (var p = 0; p < pointNames.length; p++) {
			// Get the latest value for this point.
			var pVal = findPointReference(pointNames[p]).latestValue().value;
			if (pVal >= refValues[p][0] &&
					pVal <= refValues[p][1]) {
				conditions[p] = true;
			} else {
				conditions[p] = false;
			}
		}

		return evaluateLogic(conditions, logic);
	};

	/**
	 * Error checking method: check if a number of point error states
	 * are in some reference states.
	 * @param {array} pointNames The names of the points to check.
	 * @param {array} refValues The comparison states.
	 * @param {string} logic The way to handle multiple checks.
	 */
	usableErrorRoutines.errorState = function(pointNames, refValues, logic) {
		logic = defaultLogic(pointNames, logic);
		
		// Go through the points.
		var conditions = [];
		for (var p = 0; p < pointNames.length; p++) {
			// Get the latest error state for this point.
			var pVal = findPointReference(pointNames[p]).latestValue().errorState;
			if (pVal == refValues[p]) {
				conditions[p] = true;
			} else {
				conditions[p] = false;
			}
		}

		return evaluateLogic(conditions, logic);
	};

	
	/**
	 * Start monitoring a new point.
	 * @param {string} pointName The name of the MoniCA point to add.
	 */
	var addPoint = function(pointName) {
		// Check that we have a MoniCA connection.
		if (hasMoniCA() === false) {
			return;
		}
		
		// Check that the point name exists on the MoniCA server.
		if (spec.monica.isPoint(pointName) === false) {
			return;
		}
		
		// Add the point and get its reference.
		var nPointObject = {
			name: pointName
		};
		var pRef = spec.monica.addPoints([ pointName ]);
		nPointObject.reference = pRef[0];

		// Direct the server to call our callback function when the point updates.
		nPointObject.reference.addCallback(pointUpdated);
		
		// Add it to our list.
		relatedPoints.push(nPointObject);
	};

	/**
	 * This routine gets called when a point we handle is updated.
	 * @param {object} pointRef The point that has been updated.
	 */
	var pointUpdated = function(pointRef) {
		// Which point has been updated?
		var pName = pointRef.getPointDetails().name;
		
		// Do we evaluate on this point?
		if (typeof checkCondition.point !== 'undefined' &&
				(checkCondition.point === '*' ||
				 checkCondition.point === pName)) {
			// Call our evaluator.
			evaluateCondition();
		}
		if (typeof checkCondition.points !== 'undefined' &&
				dojo.isArray(checkCondition.points)) {
			for (var i = 0; i < checkCondition.points.length; i++) {
				if (checkCondition.points[i] === pName) {
					// Call our evaluator.
					evaluateCondition();
				}
			}
		}
	};

	/**
	 * Determine whether an error condition is met or not.
	 * @param {object} eCond The error condition to evaluate.
	 */
	var evaluateErrorCondition = function(eCond) {
		var isError = false;
		eCond.logic = defaultLogic(eCond.conditions, eCond.logic);
		
		// Use the action functions to determine the result.
		var condRes = [];
		for (var cc = 0; cc < eCond.conditions.length; cc++) {
			condRes[cc] =
				eCond.conditions[cc].action(eCond.conditions[cc].pointNames,
																		eCond.conditions[cc].values);
			// Make some object parameters to help the user make their
			// messages.
			eCond.conditions[cc].pointDetails = [];
			eCond.conditions[cc].pointValues = [];
			for (var pc = 0; pc < eCond.conditions[cc].pointNames.length; pc++) {
				eCond.conditions[cc].pointDetails[pc] =
					findPointReference(eCond.conditions[cc].pointNames[pc]).
					getPointDetails();
				eCond.conditions[cc].pointValues[pc] =
					findPointReference(eCond.conditions[cc].pointNames[pc]).latestValue();
			}
		}
		
		isError = evaluateLogic(condRes, eCond.logic);
		
		// Set a triggered flag.
		eCond.triggered = isError;

		
		// If it is in error, generate any user-specified messages.
		if (isError === true &&
				dojo.isFunction(eCond.msgFn) === true) {
			eCond.message = eCond.msgFn(eCond.conditions);
		}
		
		return isError;
	};
	
	/**
	 * Find the point reference associated with a named point.
	 * @param {string} pName The name of the point to search for.
	 */
	var findPointReference = function(pName) {
		for (var k = 0; k < relatedPoints.length; k++) {
			if (relatedPoints[k].name === pName) {
				return relatedPoints[k].reference;
			}
		}
		
		return null;
	};
		
	/**
	 * This routine gets called when we are told to evaluate our status.
	 */
	var evaluateCondition = function() {
		// If we've been asked to call a function every time, we do that now.
		checkCall(that);
		
		var inError = false;

		// Go through each of the error conditions we have and evaluate them.
		for (var i = 0; i < errorConditions.length; i++) {
			inError = inError || 
				evaluateErrorCondition(errorConditions[i]);
		}
		
		// If we've determined that we have an error condition, we call the
		// specified error callback.
		if (inError === true) {
			errorCall(that);
		}
	};

	/**
	 * Interpret our conditions object, so we can start a timer if necessary.
	 */
	var interpretCheckConditions = function() {
		// Stop our current timer.
		evaluateTimer.stop();
		
		// Check if we have a time component in the options.
		if (typeof checkCondition.time !== 'undefined') {
			evaluateTimer.setInterval(checkCondition.time * 1000);
			evaluateTimer.onTick = evaluateCondition;
			evaluateTimer.start();
		}
		
		// Do we set a calling function?
		if (typeof checkCondition.callback !== 'undefined' &&
				dojo.isFunction(checkCondition.callback)) {
			checkCall = checkCondition.callback;
			errorCall = checkCondition.callback;
		}
		if (typeof checkCondition.errorCallback !== 'undefined' &&
				dojo.isFunction(checkCondition.errorCallback)) {
			errorCall = checkCondition.errorCallback;
		}
	};

	/**
	 * Add a new check to the error conditions.
	 * @param {object} condition The specification of the error condition.
	 */
	var addErrorCheck = function(condition) {
		// Check for an array of conditions.
		if (typeof condition.conditions === 'undefined' ||
				dojo.isArray(condition.conditions) === false) {
			return;
		}

		// Go through the conditions one at a time.
		var allActionsKnown = true;
		for (var cc = 0; cc < condition.conditions.length; cc++) {
			// Check for point names.
			var allPointsKnown = true;
			if (dojo.isArray(condition.conditions[cc].pointNames) === false) {
				allPointsKnown = false;
			} else {
				for (var cp = 0; cp < condition.conditions[cc].pointNames.length;
						 cp++) {
					allPointsKnown = allPointsKnown &&
						checkForPoint(condition.conditions[cc].pointNames[cp]);
				}
			}

			// Check for a known action.
			var actionKnown = false;
			for (var ca = 0; ca < knownErrorActions.length; ca++) {
				if (condition.conditions[cc].action ===
						knownErrorActions[ca]) {
					actionKnown = true;
					// Replace this string with the appropriate function.
					condition.conditions[cc].action =
						usableErrorRoutines[knownErrorActions[ca]];
				} else if (dojo.isFunction(condition.conditions[cc].action)) {
					actionKnown = true;
				}
			}

			// Check for the right number of values.
			var allValuesPresent = true;
			if (dojo.isArray(condition.conditions[cc].values) === false) {
				allValuesPresent = false;
			} else if (condition.conditions[cc].pointNames.length !== 
								 condition.conditions[cc].values.length) {
				allValuesPresent = false;
			}
			
			// Add it all up.
			allActionsKnown = allActionsKnown && allPointsKnown &&
				allValuesPresent;
		}
		
		if (allActionsKnown === false) {
			return;
		}
		
		// All the checks have been satisfied, add it to the list.
		errorConditions.push(condition);
	};
	
	/**
	 * Check for a named point.
	 * @param {string} pName The name of the point to check for.
	 */
	var checkForPoint = function(pName) {
		for (var j = 0; j < relatedPoints.length; j++) {
			if (pName === relatedPoints[j].name) {
				return true;
			}
		}
		
		return false;
	};
	
	/**
	 * Check for a known error action.
	 * @param {object} eCond The object to check for a known error action.
	 */
	var checkForAction = function(eCond) {
		for (var j = 0; j < knownErrorActions.length; j++) {
			if (typeof eCond[knownErrorActions[j]] !== 'undefined') {
				return true;
			}
		}
		
		return false;
	};

	/**
	 * Find all the error conditions referring to a specified point name.
	 * @param {string} pointName The name of the point to search for.
	 */
	var errorConditionSearch = function(pointName) {
		var foundConditions = [];
		
		for (var i = 0; i < errorConditions.length; i++) {
			if (dojo.isArray(errorConditions[i].pointNames) === true) {
				for (var j = 0; j < errorConditions[i].pointNames.length; j++) {
					if (errorConditions[i].pointNames[j] === pointName) {
						foundConditions.push(errorConditions[i]);
						break;
					}
				}
			} else {
				if (errorConditions[i].pointNames === pointName) {
					foundConditions.push(errorConditions[i]);
				}
			}
		}
		
		return foundConditions;
	};

	// Our public methods.
	/**
	 * Start monitoring new point(s).
	 * @param {array} nPoints A single new point, or an array of points.
	 */
	that.addPoints = function(nPoints) {
		if (dojo.isArray(nPoints)) {
			// We have more than one point.
			for (var k = 0; k < nPoints.length; k++) {
				if (dojo.isString(nPoints[k])) {
					addPoint(nPoints[k]);
				}
			}
		} else if (dojo.isString(nPoints)) {
			addPoint(nPoints);
		}
		
		// Get the point descriptions.
		spec.monica.getDescriptions();
	};

	/**
	 * Set the conditions we will use to determine when we should
	 * evaluate our state.
	 * @param {object} evaluationOptions The conditions object.
	 */
	that.setCheckConditions = function(evaluationOptions) {
		// As long as the passed argument is an object, we simply
		// copy it over to the checkCondition variable.
		if (evaluationOptions !== null &&
				dojo.isFunction(evaluationOptions) === false &&
				dojo.isArray(evaluationOptions) === false &&
				dojo.isObject(evaluationOptions) === true) {
			checkCondition = evaluationOptions;
		}
		
		// Interpret what we received.
		interpretCheckConditions();
	};

	/**
	 * Add some error conditions.
	 * @param {array} nConditions A single new condition, or an array
	 *                            of conditions.
	 */
	that.addErrorConditions = function(nConditions) {
		if (dojo.isArray(nConditions)) {
			// We have more than one condition.
			for (var k = 0; k < nConditions.length; k++) {
				if (dojo.isObject(nConditions[k])) {
					addErrorCheck(nConditions[k]);
				}
			}
		} else if (dojo.isObject(nConditions)) {
			addErrorCheck(nConditions);
		}
	};

	/**
	 * Return the MoniCA references for our points.
	 */
	that.getPointInformation = function() {
		return relatedPoints;
	};

	/**
	 * Return the MoniCA reference for a single point.
	 * @param {string} pointName The name of the point to find and return.
	 */
	that.getSinglePoint = function(pointName) {
		return findPointReference(pointName);
	};

	/**
	 * Return all the objects that are triggering an error condition.
	 */
	that.getErrorConditions = function() {
		var errorConditionsNow = [];
		for (var ec = 0; ec < errorConditions.length; ec++) {
			if (errorConditions[ec].triggered === true) {
				errorConditionsNow.push(errorConditions[ec]);
			}
		}
		
		return errorConditionsNow;
	};
	
	return that;
};

/**
 * This function allows the user to compute different astronomically
 * useful time quantities, such as sidereal time and MJD. It also makes it
 * easy to switch time zones reliably.
 * @param {object} spec Specifications for the object setup.
 * @param {object} my   An object reference that this object should be able
 *                      to access.
 */
var atnfTime = function(spec, my) {
	
	/**
	 * The object we return to our caller.
	 * @type {object}
	 */
	var that = {};

	// Set some useful defaults in the spec object.
	spec = spec || {};
	
	/**
	 * Our time, which by default is set now.
	 * @type {Date}
	 */
	var time = new Date();

	/**
	 * Our location.
	 * @type {object}
	 */
	var location;
	
	/**
	 * Some locations that we know about.
	 * @type {array}
	 */
	var locations = [
		{
			telescope: 'ATCA',
			longitude: atnfAngle(149.550138889),
			latitude: atnfAngle(-30.312884722),
			timezoneOffset: 600
		},
    {
      telescope: 'Parkes',
      latitude: atnfAngle('-32d59m54.263s'),
      longitude: atnfAngle('148d15m48.636s'),
      timezoneOffset: 600
    },
    {
      telescope: 'Mopra',
      latitude: atnfAngle('-31:16:04'),
      longitude: atnfAngle('149:05:59'),
      timezoneOffset: 600
    }
	];

	// Our methods follow.
	
	// Our private methods.
	/**
	 * Initialise our instance.
	 */
	var initialise = function() {
		// Check for our constructor options.
		if (spec.utcString) {
			// The user has supplied us with a string representation of
			// the time we should be set to.
			var calTime = string2cal(spec.utcString);
			cal2time(calTime);
		} else if (spec.date) {
			// The user has supplied a Date object to match.
			time.setTime(spec.date.getTime());
		}
		
		// Do we have a specified location?
		if (spec.location &&
				typeof spec.location.telescope !== 'undefined' &&
				typeof spec.location.longitude !== 'undefined' &&
				typeof spec.location.latitude !== 'undefined' &&
				typeof spec.location.timezoneOffset !== 'undefined') {
			location = spec.location;
		} else {
			// By default we are located at ATCA.
			setLocation('ATCA');
		}
	};

	/**
	 * Set the location based on the name of the telescope.
	 * @param {string} telescope The name of the telescope.
	 */
	var setLocation = function(telescope) {
		for (var i = 0; i < locations.length; i++) {
			if (locations[i].telescope.toLowerCase() === telescope.toLowerCase()) {
				location = locations[i];
			}
		}
	};
	
	/**
	 * Turn a string representation of the time into a calendar object.
	 * @param {string} stringTime The string representation of the time.
	 */
	var string2cal = function(stringTime) {
		if (!dojo.isString(stringTime)) {
			return null;
		}
		
		var calendarObj = {};
		
		var tElements = stringTime.split(/[\s\-\:T]/g);
		if (tElements[0]) {
			calendarObj.year = tElements[0];
		} else {
			return null;
		}
		if (tElements[1]) {
			calendarObj.month = tElements[1];
		} else {
			return null;
		}
		if (tElements[2]) {
			calendarObj.day = tElements[2];
		} else {
			return null;
		}
		if (tElements[3]) {
			calendarObj.hour = tElements[3];
		} else {
			return null;
		}
		if (tElements[4]) {
			calendarObj.minute = tElements[4];
		} else {
			return null;
		}
		if (tElements[5]) {
			calendarObj.second = tElements[5];
		} else {
			return null;
		}
		
		return calendarObj;
	};
	
	/**
	 * Set our object time by a UTC calendar object.
	 * @param {object} calendarObj The calendar object specifying the time.
	 */
	var cal2time = function(calendarObj) {
		if (!calendarObj) {
			return;
		}
		
		if (typeof calendarObj.year !== 'undefined' &&
				typeof calendarObj.month !== 'undefined' &&
				typeof calendarObj.day !== 'undefined' &&
				typeof calendarObj.hour !== 'undefined' &&
				typeof calendarObj.minute !== 'undefined' &&
				typeof calendarObj.second !== 'undefined') {
			time.setUTCFullYear(calendarObj.year);
			time.setUTCMonth(calendarObj.month - 1);
			time.setUTCDate(calendarObj.day);
			time.setUTCHours(calendarObj.hour);
			time.setUTCMinutes(calendarObj.minute);
			time.setUTCSeconds(calendarObj.second);
			time.setUTCMilliseconds(0);
		}
	};

	/**
	 * Turn a calendar object into an MJD.
	 * @param {object} calendarObj The calendar object.
	 */
	var cal2mjd = function(calendarObj) {
		var dayFraction = getDayFraction(calendarObj);
		var m, y;
		if (calendarObj.month <= 2) {
			m = calendarObj.month + 9;
			y = calendarObj.year - 1;
		} else {
			m = calendarObj.month - 3;
			y = calendarObj.year;
		}
		
		var c = parseInt(y / 100);
		y -= c * 100;
		var x1 = parseInt(146097.0 * c / 4.0);
		var x2 = parseInt(1461.0 * y / 4.0);
		var x3 = parseInt((153.0 * m + 2.0) / 5.0);
		
		var mjd = x1 + x2 + x3 + calendarObj.day - 678882 + dayFraction;
		
		return mjd;
	};
	
	/**
	 * Calculate the fraction of the day from a calendar object.
	 * @param {object} calendarObj The calendar object.
	 */
	var getDayFraction = function(calendarObj) {
		var dayFrac = (calendarObj.hour + calendarObj.minute / 60 +
											 calendarObj.second / 3600) / 24;
		
		return dayFrac;
	};

	/**
	 * Turn a day fraction into a partial calendar object.
	 * @param {Number} dayFrac The day fraction.
	 */
	var dayFraction2cal = function(dayFrac) {
		var calendarObj = {};
		var tmp = dayFrac * 24.0;
		calendarObj.hour = Math.floor(tmp);
		tmp -= calendarObj.hour;
		tmp *= 60.0;
		calendarObj.minute = Math.floor(tmp);
		tmp -= calendarObj.minute;
		tmp *= 60.0;
		calendarObj.second = Math.floor(tmp);
		
		return calendarObj;
	};

	/**
	 * Turn any time into a localtime calendar object.
	 * @param {Date} lTime The time to convert.
	 */
	var anytime2localCal = function(lTime) {
		var calendarObj = {};
		
		calendarObj.year = lTime.getFullYear();
		calendarObj.month = lTime.getMonth() + 1;
		calendarObj.day = lTime.getDate();
		calendarObj.hour = lTime.getHours();
		calendarObj.minute = lTime.getMinutes();
		calendarObj.second = lTime.getSeconds();
		
		return calendarObj;
	};

	/**
	 * Turn any time into a UTC calendar object.
	 * @param {Date} uTime The time to convert.
	 */
	var anytime2utcCal = function(uTime) {
		var calendarObj = {};
		
		calendarObj.year = uTime.getUTCFullYear();
		calendarObj.month = uTime.getUTCMonth() + 1;
		calendarObj.day = uTime.getUTCDate();
		calendarObj.hour = uTime.getUTCHours();
		calendarObj.minute = uTime.getUTCMinutes();
		calendarObj.second = uTime.getUTCSeconds();
		
		return calendarObj;
	};
	
	/**
	 * Turn our time into a calendar object.
	 */
	var time2cal = function() {
		return anytime2utcCal(time);
	};

	/**
	 * Turn an MJD into the GMST.
	 * @param {Number} mjd The MJD.
	 */
	var mjd2gmst = function(mjd) {
		/**
		 * The Julian date at the start of the epoch.
		 */
		var jdJ2000 = 2451545.0;
		/**
		 * The number of days in a century.
		 */
		var jdCentury = 36525.0;
		/**
		 * Conversion factor between solar and sidereal time.
		 */
		var solar2sidereal = 1.002737909350795;
		
		var dUT1 = 0.0;
		if (typeof location.dUT1 !== 'undefined') {
			dUT1 = location.dUT1;
		}
		
		var a = 101.0 + 24110.54841/86400.0;
		var b = 8640184.812866/86400.0;
		var e = 0.093104/86400.0;
		var d = 0.0000062/86400.0;
		
		var tu = (Math.floor(mjd) - (jdJ2000 - 2400000.5)) / jdCentury;
		var sidTim = a + tu * (b + tu * (e - tu * d));
		sidTim -= Math.floor(sidTim);
		if (sidTim < 0.0) {
			sidTim += 1.0;
		}
		
		var gmst = sidTim + (mjd - Math.floor(mjd) + dUT1 / 86400.0) *
			solar2sidereal;
		while(gmst < 0.0) {
			gmst += 1.0;
		}
		while (gmst > 1.0) {
			gmst -= 1.0;
		}

		return gmst;
	};

	/**
	 * Turn a GST into the LMST, according to our location.
	 * @param {Number} gmst The GMST.
	 */
	var gmst2lmst = function(gmst) {
		var lst = gmst + location.longitude.toTurns();
		while (lst > 1.0) {
			lst -= 1.0;
		}
		while (lst < 0.0) {
			lst += 1.0;
		}
		var lmst = lst;

		return lmst;
	};
	
	/**
	 * Turn a calendar object into a string.
	 * @param {object} calendarObj The calendar object.
	 * @param {string} timeFormat An optional format specification.
	 */
	var cal2string = function(calendarObj, timeFormat) {
		timeFormat = timeFormat || '%y-%m-%d %H:%M:%S';

		// Some days.
		var dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
		var monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
											'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
		
		var timeString = timeFormat;
		var r;
		// Go through our supported specifiers and replace them with the
		// proper quantities.
		if (/\%y/.test(timeString) === true) {
			// The year.
			r = calendarObj.year;
			timeString = timeString.replace(/\%y/, '' + r);
		} 
		if (/\%m/.test(timeString) === true) {
			// The month.
			r = zeroPad(calendarObj.month);
			timeString = timeString.replace(/\%m/, r);
		}
		if (/\%d/.test(timeString) === true) {
			// The date.
			r = zeroPad(calendarObj.day);
			timeString = timeString.replace(/\%d/, r);
		}
		if (/\%H/.test(timeString) === true) {
			// The hour.
			r = zeroPad(calendarObj.hour);
			timeString = timeString.replace(/\%H/, r);
		}
		if (/\%M/.test(timeString) === true) {
			// The minute.
			r = zeroPad(calendarObj.minute);
			timeString = timeString.replace(/\%M/, r);
		}
		if (/\%S/.test(timeString) === true) {
			// The second.
			r = zeroPad(calendarObj.second);
			timeString = timeString.replace(/\%S/, r);
		}
		if (/\%O/.test(timeString) === true) {
			// The month name.
			r = monthNames[calendarObj.month - 1];
			timeString = timeString.replace(/\%O/, r);
		}
		
		return timeString;
	};

	/**
	 * Make a number into a string, padding it with a zero if it's
	 * smaller than 10.
	 * @param {Number} n The number.
	 */
	var zeroPad = function(n) {
		var N;
		if (n < 10) {
			N = '0' + n;
		} else {
			N = '' + n;
		}
		
		return N;
	};

	/**
	 * Return the difference between a specified offset and the
	 * timezone on this computer.
	 * @param {number} offsetMinutes An offset in minutes.
	 */
	var tzDifference = function(offsetMinutes) {
		// Get the current timezone offset for our dates.
		var cOffset = -1 * time.getTimezoneOffset();
		// Now subtract this from the requested offset.
		var rOffset = offsetMinutes - cOffset;

		return rOffset;
	};
	
	// Our public methods.
	/**
	 * Return our MJD.
	 */
	that.mjd = function() {
		var calObj = time2cal();
		var mjd = cal2mjd(calObj);
		
		return mjd;
	};

	/**
	 * Return our time in string form.
	 * @param {string} timeFormat An optional time format specification.
	 */
	that.timeString = function(timeFormat) {
		var calObj = time2cal();
		var ts = cal2string(calObj, timeFormat);
		
		return ts;
	};

	/**
	 * Return our time in Date object form.
	 */
	that.time = function() {
		return time;
	};

	/**
	 * Return the local time at the telescope in string form.
	 * @param {string} timeFormat An optional time format specification.
	 */
	that.localtimeString = function(timeFormat) {
		var rDate = dojo.date.add(time, 'minute',
			tzDifference(location.timezoneOffset));
		var calObj = anytime2localCal(rDate);
		var ts = cal2string(calObj, timeFormat);
		
		return ts;
	};
	
	/**
	 * Return our LMST as a fraction of a day.
	 */
	that.lmst = function() {
		var calObj = time2cal();
		var mjd = cal2mjd(calObj);
		var gmst = mjd2gmst(mjd);
		var lmst = gmst2lmst(gmst);
		
		return lmst;
	};
	
	/**
	 * Return our LMST as a string.
	 * @param {string} timeFormat An optional time format specification.
	 */
	that.lmstString = function(timeFormat) {
		timeFormat = timeFormat || '%H:%M:%S';
		var calObj = time2cal();
		var mjd = cal2mjd(calObj);
		var gmst = mjd2gmst(mjd);
		var lmst = gmst2lmst(gmst);
		var lmstCal = dayFraction2cal(lmst);
		var ts = cal2string(lmstCal, timeFormat);
		
		return ts;
	};
	
	/**
	 * Update our time to right now.
	 */
	that.now = function() {
		time = new Date();
		return that;
	};

	/**
	 * Return the location we're using.
	 */
	that.getLocation = function() {
		return location;
	};

	/**
	 * Set the location to use.
   * @param {string} locName The name of the location to use.
	 */
	that.setLocation = function(locName) {
    setLocation(locName);
    
    return that;
	};
	
	// Perform the initialisation.
	initialise();
	
	return that;
};

/**
 * This function allows the user to specify an astronomical source and then
 * get useful information about it, such as its rise and set times, its
 * coordinates in other systems, etc.
 * @param {object} spec Specifications for the object setup.
 * @param {object} my   An object reference that this object should be able
 *                      to access.
 */
var atnfSource = function(spec, my) {
	/**
	 * The object we return to our callers.
	 * @type {object}
	 */
	var that = {};
	
	// Set some sensible defaults in the spec object.
	spec = spec || {};
	spec.name = spec.name || '';
	spec.coordinate = spec.coordinate || null;
	
	/**
	 * The name of this source, or primary identifier.
	 * @type {string}
	 */
	var sourceName;
	
	/**
	 * The sky coordinates for the source.
	 * @type {atnfSkyCoordinate}
	 */
	var sourceCoordinate;
	
	/**
	 * Alternative names for this source.
	 * @type {array}
	 */
	var sourceAlternativeNames = [];

	/**
	 * Is construction complete?
	 * @type {Boolean}
	 */
	var sourceValid = false;
	
	/**
	 * Time handler to help us calculate local source locations.
	 * @type {atnfTime}
	 */
	var timeHandler = atnfTime();

  /**
   * A set of any other parameters the user may want the
   * source to have.
   * @type {object}
   */
  var sourceParameters = spec.parameters || {};
	
	// Our methods follow.

	// Our private methods.
	/**
	 * Try to make a new source object using the information we were given
	 * during construction.
	 */
	var initialise = function() {
		// Check for a valid name.
		if (spec.name === '') {
			// Not a valid name.
			return;
		}
		
		// Check for something in the coordinate field.
		if (spec.coordinate === null) {
			return;
		}
		
		// Is the coordinate already a sky coordinate?
		var nsc;
		if (atnfLibrary.isSkyCoordinate(spec.coordinate) === false) {
			// Try to make it a sky coordinate.
			nsc = atnfSkyCoordinate(spec.coordinate);
			// Check if the assignment worked.
			if (atnfLibrary.isSkyCoordinate(nsc) === false) {
				// No, we've failed.
				return;
			}
		} else {
			nsc = spec.coordinate;
		}
		
		// We have enough information to finish construction.
		sourceName = spec.name;
		sourceCoordinate = nsc;
		sourceValid = true;
		
		// Check for alternative names.
		if (typeof spec.altNames !== 'undefined' &&
				dojo.isArray(spec.altNames) === true) {
			// Check that each element is a string.
			for (var i = 0; i < spec.altNames.length; i++) {
				if (dojo.isString(spec.altNames[i]) === true) {
					sourceAlternativeNames.push(spec.altNames[i]);
				}
			}
		}
	};

	/**
	 * Calculate the source's hour angle.
	 */
	var hourAngle = function() {
		// We need our coordinates in J2000 for this.
		var ourJco = sourceCoordinate.toJ2000();
		// Get the RA as a number of turns.
		var ourRa = ourJco.rightAscension.toTurns();
		
		// Get the LST for the time provided.
		var ourLst = timeHandler.lmst();
		
		var hourAngle1 = ourLst - ourRa;
		var hourAngle2 = ourLst + 1 - ourRa;
		var hourAngle;
    if (Math.abs(hourAngle1) < Math.abs(hourAngle2)){
      hourAngle = hourAngle1;
    } else {
      hourAngle = hourAngle2;
    }
    if (hourAngle > 0.5) {
      hourAngle -= 1.0;
    } else if (hourAngle < -0.5) {
      hourAngle = 1 - hourAngle;
    }

		return hourAngle;
	};

	/**
	 * Calculate the source's azimuth & elevation.
	 */
	var calculateAzEl = function() {
		// Get the location from our time handler.
		var ourLocation = timeHandler.getLocation();
		// Get the hour angle.
		var haNow = hourAngle().turns2radians();
		// Get the source coordinates.
		var ourCoordinate = sourceCoordinate.toJ2000();
		
		var sphi = Math.sin(ourLocation.latitude.toRadians());
		var cphi = Math.cos(ourLocation.latitude.toRadians());
		var sleft = Math.sin(haNow);
		var cleft = Math.cos(haNow);
		var sright = Math.sin(ourCoordinate.declination.toRadians());
		var cright = Math.cos(ourCoordinate.declination.toRadians());
		
		var leftOut = Math.atan2(-1 * sleft, 
			-1 * cleft * sphi + sright * cphi / cright) / (2 * Math.PI);
		var rightOut = Math.asin(cleft * cright * cphi + sright * sphi) /
			(2 * Math.PI);
		
		return {
			azimuth: leftOut,
			elevation: rightOut
		};
	};

	/**
	 * Calculate the source's rise/set hour angle.
	 * @param {atnfAngle} setElevation The elevation that the source sets.
	 */
	var calculateHaSet = function(setElevation) {
		// Get the location from our time handler.
		var ourLocation = timeHandler.getLocation();
		// Get the source coordinates.
		var ourCoordinate = sourceCoordinate.toJ2000();

		var cosHaSet = (Math.cos(Math.PI / 2 - setElevation.toRadians()) -
										Math.sin(ourLocation.latitude.toRadians()) *
										Math.sin(ourCoordinate.declination.toRadians())) /
			             (Math.cos(ourCoordinate.declination.toRadians()) *
										Math.cos(ourLocation.latitude.toRadians()));
		
		return cosHaSet;
	};
	
	// Our public methods.
	/**
	 * Assemble and return all of our details.
	 */
	that.details = function() {
		if (sourceValid === true) {
			return {
				name: sourceName,
				coordinate: sourceCoordinate,
				altName: sourceAlternativeNames
			};
		} else {
			return null;
		}
	};

	/**
	 * Change our time handler to an atnfTime object that the user supplies.
	 * @param {atnfTime} timeObj The atnfTime object to use.
	 */
	that.useTime = function(timeObj) {
		if (atnfLibrary.isTime(timeObj) === true) {
			timeHandler = timeObj;
		}
		
		return that;
	};

	/**
	 * Tell our time handler to use the current time.
	 */
	that.timeIsNow = function() {
		timeHandler.now();
		
		return that;
	};

	/**
	 * Return the hour angle, as an atnfAngle.
	 */
	that.hourAngle = function() {
		var hAngle = hourAngle();
		return atnfAngle({
			value: hAngle,
			units: 'turns'
		});
	};
	
	/**
	 * Return the source's az/el as atnfAngles.
	 */
	that.getAzEl = function() {
		var azelObj = calculateAzEl();
		azelObj.azimuth = atnfAngle({
			value: azelObj.azimuth,
			units: 'turns'
		});
		azelObj.elevation = atnfAngle({
			value: azelObj.elevation,
			units: 'turns'
		});
		
		return azelObj;
	};

	/**
	 * Return the source's rise/set hour angle as an atnfAngle.
	 * @param {atnfAngle} elevationLimit The rise/set elevation limit.
	 */
	that.setHourAngle = function(elevationLimit) {
		var setHa = calculateHaSet(elevationLimit);
		
		var setAngle;
		if (setHa > 1 || setHa < -1) {
			// This source never rises (>1) or never sets (<-1).
			setAngle = atnfAngle({
				value: setHa,
				units: 'turns'
			});
		} else {
			setAngle = atnfAngle({
				value: (Math.acos(setHa) / (Math.PI * 2)),
				units: 'turns'
			});
		}
		
		return setAngle;
	};

	/**
	 * Return the time until the source next crosses a specified elevation.
	 * @param {atnfAngle} crossElevation The elevation crossing angle.
	 */
	that.timeUntilElevation = function(crossElevation) {
		// Get the hour angle at our elevation crossing.
		var crossHa = that.setHourAngle(crossElevation);
		// Get the current hour angle.
		var currHa = that.hourAngle();
		
		if (crossHa.toTurns() > 1 || crossHa.toTurns() < -1) {
			return crossHa;
		}
		
		var haValue;
		if (crossHa.toTurns() > Math.abs(currHa.toTurns())) {
			haValue = crossHa.toTurns() - currHa.toTurns();
		} else {
			haValue = (-1 * crossHa.toTurns()) - currHa.toTurns();
			if (haValue < 0) {
				haValue += 1;
			}
		}

		return atnfAngle({
			value: haValue,
			units: 'turns'
		});
	};

  /**
   * Return a named source parameter, or a set of parameters, or
   * all parameters.
   * @param {variable} paramList The parameters to return.
   *                             Leave blank to receive all.
   */
  that.getParameters = function(paramList) {
    var retObj = {};
    
    if (typeof paramList === 'undefined') {
      // Return all the parameters.
      retObj = dojo.clone(sourceParameters);
    } else if (dojo.isString(paramList) === true) {
      // Return only a single parameter.
      if (typeof sourceParameters[paramList] !== 'undefined') {
        retObj[paramList] = dojo.clone(sourceParameters[paramList]);
      }
    } else if (dojo.isArray(paramList) === true) {
      // Return all the named parameters.
      for (var i = 0; i < paramList.length; i++) {
        if (typeof sourceParameters[paramList[i]] !== 'undefined') {
          retObj[paramList[i]] = dojo.clone(sourceParameters[paramList[i]]);
        }
      }
    }
    
    return retObj;
  };
	
	// Try to initialise.
	initialise();
	
	return that;
	
};

/**
 * This function queries the pulsar catalogue.
 * @param {object} spec Specifications for the object setup.
 * @param {object} my   An object reference that this object should be able
 *                      to access.
 */
var atnfPulsarResolver = function(spec, my) {
  	/**
	 * The object we return to our callers.
	 * @type {object}
	 */
	var that = {};
	
	// Set some sensible defaults in the spec object.
	spec = spec || {};

  /**
   * The webserver to query.
   * @type {string}
   */
  spec.resolverServer = spec.resolverServer ||
    'serpens.atnf.csiro.au:8888';
  
	/**
	 * The base location for name resolution.
	 * @type {string}
	 */
  spec.resolverBase = spec.resolverBase || 'psrcat';

	/**
	 * The name of the source to search for.
	 * @type {string}
	 */
	spec.sourceName = spec.sourceName || '';
	
	/**
	 * Our promise for when the name has been resolved.
	 * @type {Deferred}
	 */
	var resolvePromise = null;
	
	// Our methods follow.

	// Our private methods.
  	/**
	 * The routine used to do the name resolution.
	 */
	var nameResolve = function() {
    // Make a deferred promise that we will resolve when the name
    // resolution has completed.
    resolvePromise = new dojo.Deferred();
    
    dojo.io.script.get({
      url: 'http://' + spec.resolverServer + '/' +
        spec.resolverBase + '/' + spec.sourceName,
      callbackParamName: 'callback'
    }).then(resolveActions, resolveError);
    
    return resolvePromise;
  };
  
  	/**
	 * The routine called when the name resolution has finished.
	 * @param {object} data The data coming back from the server call.
	 * @param {object} ioargs Other information.
	 */
	var resolveActions = function(data, ioargs) {
		// Check that we don't get an error.
		if (typeof data.error !== 'undefined') {
			// Something went wrong with the name resolution process.
			// We resolve our promise now with an error.
			resolvePromise.reject(data.error);
		} else if (typeof data.RAJ !== 'undefined') {
			// Make a new source object.
			var rSource = atnfSource({
				name: spec.sourceName,
				coordinate: {
					ra: {
						value: data.RAJ.value.hexa2turns({ units: 'hours' }),
						units: 'turns'
					},
					dec: data.DECJ.value,
					frame: 'J2000'
        },
        parameters: {
          'P0': data['P0'],
          'DM': data['DM']
				}
			});
			// Resolve our promise now by returning the source.
			resolvePromise.resolve(rSource);
		}
	};
	
	/**
	 * The routine called if our call to the name resolving script fails.
	 * @param {object} err Information about the error.
	 * @param {object} ioargs Other information.
	 */
	var resolveError = function(err, ioargs) {
		// We resolve our promise now with an error.
		resolvePromise.reject('Call to server script "' + 
			spec.resolverServer + '" failed.');
	};
	
	// Our public methods.
	/**
	 * Start the resolution process.
	 */
	that.resolveName = function() {
		// Check we actually have a name to resolve.
		if (spec.sourceName === '') {
			return null;
		} else {
			return nameResolve();
		}
	};

	/**
	 * Set the source name to search for after construction.
	 * @param {string} sourceName The name of the source to search for.
	 */
	that.setSourceName = function(sourceName) {
		if (dojo.isString(sourceName)) {
			spec.sourceName = sourceName;
		}
		
		// Return ourselves to allow for method chains.
		return that;
	};
	
	return that;

};

/**
 * This function makes it easy to take an astronomical source name and
 * get its source coordinates using some online services.
 * 
 * Source query server-side expects only a 'name' parameter, to search for.
 * You will get back a JSON object describing the position.
 * 
 * @param {object} spec Specifications for the object setup.
 * @param {object} my   An object reference that this object should be able
 *                      to access.
 */
var atnfSourceResolver = function(spec, my) {
	/**
	 * The object we return to our callers.
	 * @type {object}
	 */
	var that = {};
	
	// Set some sensible defaults in the spec object.
	spec = spec || {};

	/**
	 * The location of the script to call for name resolution.
	 * @type {string}
	 */
	spec.resolverScript = spec.resolverScript ||
		'/cgi-bin/Calibrators/new/sourcequery.pl';

	/**
	 * The name of the source to search for.
	 * @type {string}
	 */
	spec.sourceName = spec.sourceName || '';
	
	/**
	 * Our promise for when the name has been resolved.
	 * @type {Deferred}
	 */
	var resolvePromise = null;
	
	// Our methods follow.

	// Our private methods.
	/**
	 * The routine used to do the name resolution.
	 */
	var nameResolve = function() {
		// Make a deferred promise that we will resolve when the
		// name resolution has completed.
		resolvePromise = new dojo.Deferred();
		
		var postDeferred = dojo.xhrPost({
			url: spec.resolverScript,
			sync: false,
			content: { 
				name: spec.sourceName
			},
			handleAs: 'json'
		});
		
		// Set up the data and error handlers.
		postDeferred.then(resolveActions, resolveError);
		
		return resolvePromise;
	};
	
	/**
	 * The routine called when the name resolution has finished.
	 * @param {object} data The data coming back from the server call.
	 * @param {object} ioargs Other information.
	 */
	var resolveActions = function(data, ioargs) {
		// Check that we don't get an error.
		if (typeof data.error !== 'undefined') {
			// Something went wrong with the name resolution process.
			// We resolve our promise now with an error.
			resolvePromise.reject(data.error);
		} else if (typeof data.position !== 'undefined') {
			// Make a new source object.
			var rSource = atnfSource({
				name: data.name,
				coordinate: {
					ra: { 
						value: data.position.ra.hexa2turns({ units: 'hours' }),
						units: 'turns'
					},
					dec: data.position.dec,
					frame: data.position.epoch
				}
			});
			// Resolve our promise now by returning the source.
			resolvePromise.resolve(rSource);
		}
	};
	
	/**
	 * The routine called if our call to the name resolving script fails.
	 * @param {object} err Information about the error.
	 * @param {object} ioargs Other information.
	 */
	var resolveError = function(err, ioargs) {
		// We resolve our promise now with an error.
		resolvePromise.reject('Call to server script "' + 
			spec.resolverScript + '" failed.');
	};
	
	// Our public methods.
	/**
	 * Start the resolution process.
	 */
	that.resolveName = function() {
		// Check we actually have a name to resolve.
		if (spec.sourceName === '') {
			return null;
		} else {
			return nameResolve();
		}
	};

	/**
	 * Set the source name to search for after construction.
	 * @param {string} sourceName The name of the source to search for.
	 */
	that.setSourceName = function(sourceName) {
		if (dojo.isString(sourceName)) {
			spec.sourceName = sourceName;
		}
		
		// Return ourselves to allow for method chains.
		return that;
	};
	
	return that;
};

// What follows are some new datatypes that our library will use.

/**
 * atnfAngle: a datatype used to represent angles.
 * @param {variable} constructor The angle that this object should represent.
 */
var atnfAngle = function(constructor) {
	/**
	 * The object we return to our callers.
	 * @type {object}
	 */
	var that = {};
	
	/**
	 * The value of our angle.
	 * @type {number}
	 */
	var angleValue;
	
	/**
	 * The units of the angle.
	 * @type {string}
	 */
	var angleUnits;
	
	/**
	 * Do we have an angle?
	 * @type {boolean}
	 */
	var angleSet = false;

	/**
	 * The units that we can deal with.
	 * @type {array}
	 */
	var usableUnitNames = ['degrees', 'hours', 'turns', 'radians',
												 'arcminutes', 'arcseconds'];
	
	// Our methods follow.

	// Our private methods.
	/**
	 * Use our constructor to set our values.
	 */
	var initialise = function() {
		// Check we have a constructor.
		if (typeof constructor === 'undefined') {
			return;
		}
		
		// Do different things depending the type of the constructor. 
		if (typeof constructor === 'number') {
			// We accept this number as our angle in decimal degrees.
			angleValue = constructor;
			angleUnits = 'degrees';
			angleSet = true;
		} else if (dojo.isString(constructor) === true) {
			// Try converting the string using the ATNF JS library extension.
			var t = constructor.hexa2turns();
			if (typeof t === 'number') {
				// The conversion worked, so we now have the angle in turns.
				angleValue = t;
				angleUnits = 'turns';
				angleSet = true;
			} else {
				// The conversion failed, so we try to interpret the string ourselves.
				if (/^[\d\.]+\s*\D+$/.test(constructor) === true) {
					// We have a number followed by some letters.
					var am = /^([\d\.]+)\s*(\D+)$/.exec(constructor);
					for (var u = 0; u < usableUnitNames.length; u++) {
						if (am[2].minimumMatch(usableUnitNames[u]) === true) {
							angleValue = parseFloat(am[1]);
							angleUnits = usableUnitNames[u];
							angleSet = true;
							break;
						}
					}
				}
			}
		} else if (dojo.isArray(constructor) === false &&
							 dojo.isObject(constructor) === true &&
							 constructor !== null) {
			// The constructor is an object. We check for the required parameters.
			if (typeof constructor.value !== 'undefined' &&
					typeof constructor.units !== 'undefined') {
				if (typeof constructor.value === 'number') {
					for (var u = 0; u < usableUnitNames.length; u++) {
						if (constructor.units.minimumMatch(usableUnitNames[u]) === true) {
							// We can use this constructor.
							angleValue = constructor.value;
							angleUnits = usableUnitNames[u];
							angleSet = true;
							break;
						}
					}
				}
			}
		}
	};

	/**
	 * Convert our angle into degrees, for a return value.
	 */
	var convertToDegrees = function() {
		// Check for our own units.
		if (angleUnits === 'degrees') {
			return angleValue;
		} else if (angleUnits === 'hours') {
			return angleValue.hours2degrees();
		} else if (angleUnits === 'turns') {
			return angleValue.turns2degrees();
		} else if (angleUnits === 'radians') {
			return angleValue.radians2turns().turns2degrees();
		} else if (angleUnits === 'arcminutes') {
			return angleValue.arcmin2degrees();
		} else if (angleUnits === 'arcseconds') {
			return angleValue.arcsec2degrees();
		} else {
			// This is an error.
			return 0;
		}
	};

	/**
	 * Convert our angle into hours, for a return value.
	 */
	var convertToHours = function() {
		// Check for our own units.
		if (angleUnits === 'degrees') {
			return angleValue.degrees2hours();
		} else if (angleUnits === 'hours') {
			return angleValue;
		} else if (angleUnits === 'turns') {
			return angleValue.turns2degrees().degrees2hours();
		} else if (angleUnits === 'radians') {
			return angleValue.radians2turns().turns2degrees().degrees2hours();
		} else if (angleUnits === 'arcminutes') {
			return angleValue.arcmin2degrees().degrees2hours();
		} else if (angleUnits === 'arcseconds') {
			return angleValue.arcsec2degrees().degrees2hours();
		} else {
			// This is an error.
			return 0;
		}
	};

	/**
	 * Convert our angle into turns, for a return value.
	 */
	var convertToTurns = function() {
		// Check for our own units.
		if (angleUnits === 'degrees') {
			return angleValue.degrees2turns();
		} else if (angleUnits === 'hours') {
			return angleValue.hours2degrees().degrees2turns();
		} else if (angleUnits === 'turns') {
			return angleValue;
		} else if (angleUnits === 'radians') {
			return angleValue.radians2turns();
		} else if (angleUnits === 'arcminutes') {
			return angleValue.arcmin2degrees().degrees2turns();
		} else if (angleUnits === 'arcseconds') {
			return angleValue.arcsec2degrees().degrees2turns();
		} else {
			// This is an error.
			return 0;
		}
	};

	/**
	 * Convert our angle into radians, for a return value.
	 */
	var convertToRadians = function() {
		// Check for our own units.
		if (angleUnits === 'degrees') {
			return angleValue.degrees2turns().turns2radians();
		} else if (angleUnits === 'hours') {
			return angleValue.hours2degrees().degrees2turns().turns2radians();
		} else if (angleUnits === 'turns') {
			return angleValue.turns2radians();
		} else if (angleUnits === 'radians') {
			return angleValue;
		} else if (angleUnits === 'arcminutes') {
			return angleValue.arcmin2degrees().degrees2turns().turns2radians();
		} else if (angleUnits === 'arcseconds') {
			return angleValue.arcsec2degrees().degrees2turns().turns2radians();
		} else {
			// This is an error.
			return 0;
		}
	};
	
	/**
	 * Convert our angle into arcminutes, for a return value.
	 */
	var convertToArcminutes = function() {
		// Check for our own units.
		if (angleUnits === 'degrees') {
			return angleValue.degrees2arcmin();
		} else if (angleUnits === 'hours') {
			return angleValue.hours2degrees().degrees2arcmin();
		} else if (angleUnits === 'turns') {
			return angleValue.turns2degrees().degrees2arcmin();
		} else if (angleUnits === 'radians') {
			return angleValue.radians2turns().turns2degrees().degrees2arcmin();
		} else if (angleUnits === 'arcminutes') {
			return angleValue;
		} else if (angleUnits === 'arcseconds') {
			return angleValue.arcsec2degrees().degrees2arcmin();
		} else {
			// This is an error.
			return 0;
		}
	};

	/**
	 * Convert our angle into arcseconds, for a return value.
	 */
	var convertToArcseconds = function() {
		// Check for our own units.
		if (angleUnits === 'degrees') {
			return angleValue.degrees2arcsec();
		} else if (angleUnits === 'hours') {
			return angleValue.hours2degrees().degrees2arcsec();
		} else if (angleUnits === 'turns') {
			return angleValue.turns2degrees().degrees2arcsec();
		} else if (angleUnits === 'radians') {
			return angleValue.radians2turns().turns2degrees().degrees2arcsec();
		} else if (angleUnits === 'arcminutes') {
			return angleValue.arcmin2degrees().degrees2arcsec();
		} else if (angleUnits === 'arcseconds') {
			return angleValue;
		} else {
			// This is an error.
			return 0;
		}
	};

	
	// Our public methods.
	/**
	 * Return the value, as it is right now, as an object.
	 */
	that.value = function() {
		if (angleSet === true) {
			return {
				value: angleValue,
				units: angleUnits
			};
		} else {
			return null;
		}
	};

	/**
	 * Return the constructor.
	 */
	that.constructor = function() {
		return constructor;
	};
	
	/**
	 * Return the value as a number in the specified units.
	 * @param {string} units The units to return the number in.
	 */
	that.to = function(units) {
		if (dojo.isString(units) === false) {
			return 0;
		}
		
		var out;
		
		// Simple output if the requested units are the same as the
		// units we already have.
		if (units.minimumMatch(angleUnits) === true) {
			return angleValue;
		}
		
		if (units.minimumMatch('degrees') === true) {
			return convertToDegrees();
		} else if (units.minimumMatch('hours') === true) {
			return convertToHours();
		} else if (units.minimumMatch('turns') === true) {
			return convertToTurns();
		} else if (units.minimumMatch('radians') === true) {
			return convertToRadians();
		} else if (units.minimumMatch('arcminutes') === true) {
			return convertToArcminutes();
		} else if (units.minimumMatch('arcseconds') === true) {
			return convertToArcseconds();
		}
		
		// We should never get here.
		return 0;
	};

	/**
	 * Return the value as a number in degrees.
	 */
	that.toDegrees = function() {
		return that.to('degrees');
	};

	/**
	 * Return the value as a number in hours.
	 */
	that.toHours = function() {
		return that.to('hours');
	};

	/**
	 * Return the value as a number in turns.
	 */
	that.toTurns = function() {
		return that.to('turns');
	};

	/**
	 * Return the value as a number in radians.
	 */
	that.toRadians = function() {
		return that.to('radians');
	};

	/**
	 * Return the value as a number in arcminutes.
	 */
	that.toArcminutes = function() {
		return that.to('arcminutes');
	};

	/**
	 * Return the value as a number in arcseconds.
	 */
	that.toArcseconds = function() {
		return that.to('arcseconds');
	};

	/**
	 * Add another angle to this one and return a new angle.
	 * @param {atnfAngle} aAngle The angle to add to this one.
	 */
	that.add = function(aAngle) {
		// Check that we have been passed an atnfAngle object.
		if (atnfLibrary.isAngle(aAngle) === false) {
			return that;
		}
		
		// Get the angle's value in our units.
		var a = aAngle.to(angleUnits);
		
		// Make a new object.
		var n = atnfAngle({
			value: (a + angleValue),
			units: angleUnits
		});
		
		return n;
	};

	/**
	 * Subtract another angle from this one and return a new angle.
	 * @param {atnfAngle} aAngle The angle to subtract from this one.
	 */
	that.subtract = function(aAngle) {
		// Check that we have been passed an atnfAngle object.
		if (atnfLibrary.isAngle(aAngle) === false) {
			return that;
		}
		
		// Get the angle's value in our units.
		var a = aAngle.to(angleUnits);
		
		// Make a new object.
		var n = atnfAngle({
			value: (angleValue - a),
			units: angleUnits
		});
		
		return n;
	};

	/**
	 * Divide this angle by another angle and return a number.
	 * @param {atnfAngle} aAngle The angle to divide this one by.
	 */
	that.divide = function(aAngle) {
		// Check that we have been passed an atnfAngle object.
		if (atnfLibrary.isAngle(aAngle) === false) {
			return that;
		}
		
		// Get the angle's value in our units.
		var a = aAngle.to(angleUnits);

		return (angleValue / a);
	};
	
	// Try interpreting the constructor.
	initialise();
	
	return that;
};

/**
 * atnfSkyCoordinate: a datatype used to represent sky positions.
 * @param {variable} constructor The coordinate that this object should
 *                               represent.
 */
var atnfSkyCoordinate = function(constructor) {
	/**
	 * The object we return to our callers.
	 * @type {object}
	 */
	var that = {};

	/**
	 * Our coordinate object.
	 * @type {object}
	 */
	var coordinates = {};

	/**
	 * Our recognised coordinate frames.
	 * @type {array}
	 */
	var allowedFrames = ['J2000', 'B1950', 'Galactic'];
	
	// Our methods follow.

	// Our private methods.
	/**
	 * Initialise our instance.
	 */
	var initialise = function() {
		// Check we have a constructor.
		if (typeof constructor === 'undefined') {
			return;
		}
		
		// Do different things depending on the type of the constructor.
		// We need two quantities here, one for each of the 2D coordinates.
		if (dojo.isArray(constructor)) {
			/**
			 * With an array, the first two elements are the coordinates, and
			 * the optional third element is the frame specification, which can
			 * be one of 'J2000', 'B1950', 'Galactic'. By default, if
			 * the third element is not given, we assume 'J2000'.
			 */
			var inFrame = 'J2000';
			
			if (constructor.length === 3) {
				for (var i = 0; i < allowedFrames.length; i++) {
					if (constructor[2] === allowedFrames[i]) {
						inFrame = constructor[2];
						break;
					}
				}
			}
			
			var angles = [];
			// Convert the first two elements to atnfAngles.
			for (var i = 0; i < 2; i++) {
				angles[i] = toAtnfAngle(constructor[i]);
				if (angles[i] === null) {
					// The conversion didn't work, so we fail.
					return;
				}
			}
			
			// We now have two angles and an epoch, so we form the coordinate.
			assignCoords(inFrame, angles);

		} else if (dojo.isObject(constructor) === true &&
							 constructor !== null &&
							 dojo.isFunction(constructor) === false) {
			/**
			 * With an object, it should have at least two properties, and
			 * optionally three. If it has two, it needs one from the set
			 * [ 'ra', 'rightAscension', 'l', 'latitude' ] and one from the set
			 * [ 'dec', 'declination', 'b', 'longitude' ]. If it has three
			 * properties, the third should be called 'frame' and specify 
			 * which coordinate frame to expect.
			 */
			var inFrame = '';
			if (typeof constructor.frame !== 'undefined') {
				inFrame = constructor.frame;
			}
			
			var angles = [];
			var availableFrames = {
				'J2000': {
					coord1: ['ra', 'rightAscension'],
					coord2: ['dec', 'declination']
				},
				'B1950': {
					coord1: ['ra', 'rightAscension'],
					coord2: ['dec', 'declination']
				},
				'Galactic': {
					coord1: ['l', 'latitude'],
					coord2: ['b', 'longitude']
				}
			};
			if (inFrame !== '') {
				// Check against the specified frame.
				if (checkFrame(constructor, availableFrames[inFrame]) === null) {
					// The constructor isn't consistent with what we were given.
					return;
				}
			} else {
				for (var cFrame in availableFrames) {
					if (dojo.isArray(checkFrame(constructor, 
						availableFrames[cFrame])) === true) {
						// We've found a frame that works.
						inFrame = cFrame;
						break;
					}
				}
			}
			if (inFrame === '') {
				// We couldn't find a proper frame.
				return;
			}
			// Convert the angles to atnfAngles.
			var nArr = checkFrame(constructor, availableFrames[inFrame]);
			for (var f = 0; f < nArr.length; f++) {
				if (atnfLibrary.isAngle(constructor[nArr[f]]) === true) {
					// It is already an angle, just use it.
					angles[f] = constructor[nArr[f]];
				} else {
					angles[f] = atnfAngle(constructor[nArr[f]]);
					// Check that the constructor was interpretable.
					if (atnfLibrary.isAngle(angles[f]) === null) {
						// The atnfAngle constructor didn't work, so we fail.
						return;
					}
				}
			}
			assignCoords(inFrame, angles);
		}
	};
	
	/**
	 * Turn some quantity into an atnfAngle.
	 * @param {variable} quantity The quantity to convert.
	 */
	var toAtnfAngle = function(quantity) {
		if (atnfLibrary.isAngle(quantity) === true) {
			return quantity;
		} else {
			var oAngle = atnfAngle(quantity);
			// Check that the constructor was interpretable.
			if (atnfLibrary.isAngle(oAngle) === false) {
				// The atnfAngle constructor didn't work.
				return null;
			} else {
				return oAngle;
			}
		}
	};

	/**
	 * Assign the local coordinates object.
	 * @param {string} frame The name of the coordinate frame.
	 * @param {array} angArr The array of angles to use.
	 */
	var assignCoords = function(frame, angArr) {
		var frameCoords = {
			'J2000': ['rightAscension', 'declination'],
			'B1950': ['rightAscension', 'declination'],
			'Galactic': ['latitude', 'longitude']
		};
		
		if (typeof (frameCoords[frame]) === 'undefined') {
			// The frame isn't recognised.
			return;
		}
		
		for (var a = 0; a < frameCoords[frame].length; a++) {
			coordinates[frameCoords[frame][a]] = angArr[a];
		}
		coordinates.frame = frame;
	};
	
	/**
	 * Check for required possible object parameters.
	 * @param {object} cObject The object to check.
	 * @param {object} rObject The object to check with.
	 */
	var checkFrame = function(cObject, rObject) {
		// This routine supports any number of coordinate axes.
		var coordsMatch = [];
		var matchedCoord = [];
		var coordN = 1;
		while (typeof rObject['coord' + coordN] !== 'undefined') {
			coordsMatch[coordN - 1] = false;
			var nameC = 'coord' + coordN;
			for (var c = 0; c < rObject[nameC].length; c++) {
				if (typeof cObject[rObject[nameC][c]] !== 'undefined') {
					coordsMatch[coordN - 1] = true;
					matchedCoord[coordN - 1] = rObject[nameC][c];
					break;
				}
			}
			coordN++;
		}

		var aMatch = coordsMatch[0];
		for (var c = 1; c < coordsMatch.length; c++) {
			aMatch = aMatch && coordsMatch[c];
		}
		if (aMatch === true) {
			return matchedCoord;
		} else {
			return null;
		}
	};
	
	// Our public methods.
	/**
	 * Return the current coordinates in a specified frame.
	 * @param {string} outFrame The frame to return the coordinates in.
	 */
	that.getCoordinates = function(outFrame) {
		// Test if the coordinates have been set.
		if (typeof coordinates.frame === 'undefined') {
			// Construction was not successful.
			return null;
		}
		// If outFrame is not specified, return the coordinates in the
		// constructor frame.
		if (typeof outFrame === 'undefined') {
			return coordinates;
		}
		
		// Check if the requested coordinate frame matches our constructor.
		if (coordinates.frame === outFrame) {
			return coordinates;
		}
		
		var outCoord = {};
		var coCo;
		if (outFrame === 'J2000') {
			// Return the coordinates in J2000.
			if (coordinates.frame === 'B1950') {
				coCo = atnfCoord.fk4fk5(coordinates.rightAscension,
																coordinates.declination);
				outCoord.rightAscension = coCo.ra;
				outCoord.declination = coCo.dec;
				outCoord.frame = outFrame;
			} else if (coordinates.frame === 'Galactic') {
				coCo = atnfCoord.galfk4(coordinates.latitude,
																coordinates.longitude);
				coCo = atnfCoord.fk4fk5(coCo.ra,
																coCo.dec);
				outCoord.rightAscension = coCo.ra;
				outCoord.declination = coCo.dec;
				outCoord.frame = outFrame;
			}
		} else if (outFrame === 'B1950') {
			// Return the coordinates in B1950.
			if (coordinates.frame === 'J2000') {
				coCo = atnfCoord.fk5fk4(coordinates.rightAscension,
																coordinates.declination);
				outCoord.rightAscension = coCo.ra;
				outCoord.declination = coCo.dec;
				outCoord.frame = outFrame;
			} else if (coordinates.frame === 'Galactic') {
				coCo = atnfCoord.galfk4(coordinates.latitude,
																coordinates.longitude);
				outCoord.rightAscension = coCo.ra;
				outCoord.declination = coCo.dec;
				outCoord.frame = outFrame;
			}
		} else if (outFrame === 'Galactic') {
			// Return the coordinates in Galactic.
			if (coordinates.frame === 'J2000') {
				coCo = atnfCoord.fk5fk4(coordinates.rightAscension,
																coordinates.declination);
				coCo = atnfCoord.fk4gal(coCo.ra,
																coCo.dec);
				outCoord.latitude = coCo.l;
				outCoord.longitude = coCo.b;
				outCoord.frame = outFrame;
			} else if (coordinates.frame === 'B1950') {
				coCo = atnfCoord.fk4gal(coCo.rightAscension,
																coCo.declination);
				outCoord.latitude = coCo.l;
				outCoord.longitude = coCo.b;
				outCoord.frame = outFrame;
			}
		}
		
		// Return the coordinates.
		if (typeof outCoord.frame !== 'undefined') {
			return outCoord;
		} else {
			// This is an error as we didn't recognise the outFrame.
			return null;
		}
	};

	/**
	 * Return the coordinates in J2000.
	 */
	that.toJ2000 = function() {
		return that.getCoordinates('J2000');
	};

	/**
	 * Return the coordinates in B1950.
	 */
	that.toB1950 = function() {
		return that.getCoordinates('B1950');
	};

	/**
	 * Return the coordinates in J2000.
	 */
	that.toGalactic = function() {
		return that.getCoordinates('Galactic');
	};

	/**
	 * Calculate the angular distance from this coordinate to another.
	 * @param {atnfSkyCoordinate} target The target coordinate.
	 */
	that.distanceTo = function(target) {
		// Check the target is a sky coordinate.
		if (atnfLibrary.isSkyCoordinate(target) === false) {
			return null;
		}
		
		// We need to be in either B1950 or J2000, whichever is most convenient.
		var ourCo = coordinates;
		if (coordinates.frame === 'Galactic') {
			ourCo = that.toB1950();
		}
		
		// We need to get the target coordinates in same frame as ours.
		var targetCo = target.getCoordinates(ourCo.frame);
		
		// Return the angular distance.
		return atnfCoord.angularDistance(ourCo, targetCo);
	};
	
	// Attempt the initialisation.
	initialise();

	return that;
};

/**
 * Make a new object (as like Math) to wrap around all the library
 * helper functions.
 * @type {object}
 */
var atnfLibrary = {
	version: '1.0'
};

/**
 * Check the passed argument to see if it is an atnfAngle object.
 * @param {object} cAngle The object to check.
 */
atnfLibrary.isAngle = function(cAngle) {
	if (typeof cAngle === 'undefined') {
		return false;
	}
	if (dojo.isFunction(cAngle.to) === false) {
		return false;
	}
  // It's also not an angle unless it has had a value set.
	if (cAngle.value() === null) {
		return false;
	}
	
	return true;
};

/**
 * Check the passed argument to see if it is an atnfSkyCoordinate object.
 * @param {object} cCoordinate The object to check.
 */
atnfLibrary.isSkyCoordinate = function(cCoordinate) {
	if (typeof cCoordinate === 'undefined') {
		return false;
	}
	if (dojo.isFunction(cCoordinate.getCoordinates) === false) {
		return false;
	}
  // It's also not an angle unless it has had a value set.
	if (cCoordinate.getCoordinates() === null) {
		return false;
	}
	
	return true;
};

/**
 * Check the passed argument to see if it is an atnfSource object.
 * @param {object} cSource The object to check.
 */
atnfLibrary.isSource = function(cSource) {
	if (typeof cSource === 'undefined') {
		return false;
	}
	if (dojo.isFunction(cSource.details) === false) {
		return false;
	}
	// It's also not a source if the construction failed.
	if (cSource.details() === null) {
		return false;
	}
	
	return true;
};

/**
 * Check the passed argument to see if it is an atnfTime object.
 * @param {object} cTime The object to check.
 */
atnfLibrary.isTime = function(cTime) {
	if (typeof cTime === 'undefined') {
		return false;
	}
	if (dojo.isFunction(cTime.timeString) === false) {
		return false;
	}
	
	return true;
};

/**
 * Make a new object (as like Math) to wrap around all the coordinate
 * conversion function and constants.
 * @type {object}
 */
var atnfCoord = {
	/**
	 * Some constants that we require for coordinate conversions.
	 */
	BEPOCH: 1950.0,
	JULIAN_DAY_J2000: 2451545.0,
	JULIAN_DAYS_IN_CENTURY: 36525.0,
	D2PI: 6.283185307179586476925287,
	// A small number to avoid arithmetic problems.
	TINY: 1e-30,
	// The conversion ratio km/sec to AU/tropical century.
	VF: 21.094502,
	// The E-terms vector for FK4 <--> other coordinate system
	// transforms.
	ETERM: [ -1.62557E-06, -0.31919E-06, -0.13843E-06 ],
	// The precession matrix for FK4 <--> Galactic conversions.
	ETOG: [ [-0.066988739415, -0.872755765852, -0.483538914632],
					[+0.492728466075, -0.450346958020, +0.744584633283],
					[-0.867600811151, -0.188374601723, +0.460199784784] ],
	A: [ -1.62557e-6, -0.31919e-6, -0.13843e-6, 
			 +1.245e-3,   -1.580e-3,   -0.659e-3 ],
	AD: [ +1.245e-3, -1.580e-3, -0.659e-3 ],
	EM: [ [+0.9999256782, -0.0111820611, -0.0048579477],
				[+0.0111820610, +0.9999374784, -0.0000271765],
				[+0.0048579479, -0.0000271474, +0.9999881997],
				[-0.000551,	    -0.238565,     +0.435739],
				[+0.238514,     -0.002667,     -0.008541],
				[-0.435623,     +0.012254,     +0.002117]	],
	EMI: [ [+0.9999256795,     +0.0111814828,     +0.0048590039,
					-0.00000242389840, -0.00000002710544, -0.00000001177742],
				 [-0.0111814828,     +0.9999374849,     -0.0000271771,
					+0.00000002710544, -0.00000242392702, +0.00000000006585],
				 [-0.0048590040,     -0.0000271557,     +0.9999881946,
					+0.00000001177742, +0.00000000006585, -0.00000242404995],
				 [-0.000551,         +0.238509,         -0.435614,
					+0.99990432,       +0.01118145,       +0.00485852],
				 [-0.238560,         -0.002667,         +0.012254,
					-0.01118145,       +0.99991613,       -0.00002717],
				 [+0.435730,         -0.008541,         +0.002117,
					-0.00485852,       -0.00002716,       +0.99996684] ]
};

// We need to extend the object now with some co-dependent constants
// and functions.
// The conversion ratio radians/year to arcseconds/century.
atnfCoord.PMF = 100 * 60 * 60 * 360 / atnfCoord.D2PI;
	// The conversion ratio km/sec to AU/year multiplied by 1 arcsecond in
	// radians.
atnfCoord.VFR = (atnfCoord.VF / 100) * 0.484813681109535994e-5;

/**
 * Convert a position in polar coordinates into rectangular coordinates.
 * @param {atnfAngle} polar1 A polar angle to convert, part 1.
 * @param {atnfAngle} polar2 A polar angle to convert, part 2.
 */
atnfCoord.pol2r = function(polar1, polar2) {
	var rect = {};
	rect.x = Math.cos(polar1.toRadians()) *
		Math.cos(polar2.toRadians());
	rect.y = Math.sin(polar1.toRadians()) *
		Math.cos(polar2.toRadians());
	rect.z = Math.sin(polar2.toRadians());
		
	return rect;
};

/**
 * Convert a position in rectangular coordinates into polar coordinates.
 * @param {number} x The x-component of the rectangular coordinates.
 * @param {number} y The y-component of the rectangular coordinates.
 * @param {number} z The z-component of the rectangular coordinates.
 */
atnfCoord.r2pol = function(x, y, z) {
	var tmp = Math.atan2(y, x) / atnfCoord.D2PI;

	var polar = {};
	
	if (tmp < 0) {
		tmp += 1;
	}
	
	polar.left = atnfAngle({
		value: tmp,
		units: 'turns'
	});
	tmp = Math.sqrt(x * x + y * y + z * z);
	
	polar.right = atnfAngle({
		value: Math.asin(z / tmp) / atnfCoord.D2PI,
		units: 'turns'
	});
	
	return polar;
};

/**
 * Convert B1950.0 FK4 coordinates to J2000.0 FK5 assuming zero proper
 * motion in the FK5 frame.
 * @param {atnfAngle} bRa  The FK4/B1950.0 right ascension.
 * @param {atnfAngle} bDec The FK4/B1950.0 declination.
 */
atnfCoord.fk4fk5 = function(bRa, bDec) {
	// First convert our coordinates into the rectangular frame.
	var r0 = atnfCoord.pol2r(bRa, bDec);
	
	// Adjust vector A to give zero proper motion in FK5.
	var w = (atnfCoord.BEPOCH - 1950) / atnfCoord.PMF;
	var a1 = [];
	for (var i = 0; i < 3; i++) {
		a1[i] = atnfCoord.A[i] + w * atnfCoord.AD[i];
	}
	// Remove e-terms.
	w = r0.x * a1[0] + r0.y * a1[1] + r0.z + a1[2];
	var v1 = [];
	v1[0] = r0.x - a1[0] + w * r0.x;
	v1[1] = r0.y - a1[1] + w * r0.y;
	v1[2] = r0.z - a1[2] + w * r0.z;
	
	// Convert the position vector to the Fricke system.
	var v2 = [];
	for (i = 0; i < 6; i++) {
		w = 0;
		for (var j = 0; j < 3; j++) {
			w += atnfCoord.EM[i][j] * v1[j];
		}
		v2[i] = w;
	}
	
	// Allow for fictitious proper motion in FK4.
	w = (atnfCoord.epj(atnfCoord.epb2d(atnfCoord.BEPOCH)) - 2000) /
		atnfCoord.PMF;
	for (i = 0; i < 3; i++) {
		v2[i] += w * v2[i + 3];
	}
	
	// Go back to polar coordinates.
	var pr = atnfCoord.r2pol(v2[0], v2[1], v2[2]);
	var jAngles = {
		ra: pr.left,
		dec: pr.right
	};
	
	return jAngles;
};

/**
 * Convert J2000.0 FK5 coordinates to B1950.0 FK4.
 * @param {atnfAngle} jRa  The FK5/J2000.0 right ascension.
 * @param {atnfAngle} jDec The FK5/J2000.0 declination.
 */
atnfCoord.fk5fk4 = function(jRa, jDec) {
	// First convert our coordinates into the rectangular frame.
	var v1 = atnfCoord.pol2r(jRa, jDec);
	
	// Convert position + velocity vector to the BN system.
	var w;
	var v2 = [];
	for (var i = 0; i < 6; i++) {
		v2[i] = atnfCoord.EMI[i][0] * v1.x +
			atnfCoord.EMI[i][1] * v1.y +
			atnfCoord.EMI[i][2] * v1.z;
	}
	
	// Position vector components and magnitude.
	var x = v2[0];
	var y = v2[1];
	var z = v2[2];
	var rxyz = Math.sqrt(x * x + y * y + z * z);
	
	// Apply E-terms to the position.
	w = x * atnfCoord.A[0] + y * atnfCoord.A[1] + z * atnfCoord.A[2];
	x += atnfCoord.A[0] * rxyz - w * x;
	y += atnfCoord.A[1] * rxyz - w * y;
	z += atnfCoord.A[2] * rxyz - w * z;

	// Recompute the magnitude.
	rxyz = Math.sqrt(x * x + y * y + z * z);
	
	// Apply E-terms to both the position and velocity.
	x = v2[0];
	y = v2[1];
	z = v2[2];
	w = x * atnfCoord.A[0] + y * atnfCoord.A[1] + z * atnfCoord.A[2];
	var wd = x * atnfCoord.A[3] + y * atnfCoord.A[4] + z * atnfCoord.A[5];
	x += atnfCoord.A[0] * rxyz - w * x;
	y += atnfCoord.A[1] * rxyz - w * y;
	z += atnfCoord.A[2] * rxyz - w * z;
	// var xd = v2[3] + atnfCoord.A[3] * rxyz - wd * x;
	// var yd = v2[4] + atnfCoord.A[4] * rxyz - wd * y;
	// var zd = v2[5] + atnfCoord.A[5] * rxyz - wd * z;

	// Go back to polar coordinates.
	var pr = atnfCoord.r2pol(x, y, z);
	var bAngles = {
		ra: pr.left,
		dec: pr.right
	};
	
	return bAngles;
};
	
/**
 * Conversion of Modified Julian Date to Julian epoch.
 * @param {number} julDate The Modified Julian Date.
 */
atnfCoord.epj = function(julDate) {
	return (2000 + (julDate - 51544.5) / 365.25);
};

/**
 * Conversion of Besselian Epoch to Modified Julian Date.
 * @param {number} besEpoch The Besselian Epoch.
 */
atnfCoord.epb2d = function(besEpoch) {
	return (15019.81352 + (besEpoch - 1900) * 365.242198781);
};

/**
 * Convert an FK4 position (B1950.0) to the IAU 1958 Galactic coordinate
 * system.
 * @param {atnfAngle} bRa  The right ascension.
 * @param {atnfAngle} bDec The declination.
 */
atnfCoord.fk4gal = function(bRa, bDec) {
	// First convert our coordinates into the rectangular frame.
	var rc = atnfCoord.pol2r(bRa, bDec);
	
	// Allow for e-terms.
	var w = rc.x * atnfCoord.ETERM[0] +
		rc.y * atnfCoord.ETERM[1] +
		rc.z * atnfCoord.ETERM[2];
	var temp = [];
	temp[0] = rc.x - atnfCoord.ETERM[0] + w * rc.x;
	temp[1] = rc.y - atnfCoord.ETERM[1] + w * rc.y;
	temp[2] = rc.z - atnfCoord.ETERM[2] + w * rc.z;
	
	// Precess.
	var gal = [];
	for (var i = 0; i < 3; i++) {
		gal[i] = 0;
		for (var j = 0; j < 3; j++) {
			gal[i] += atnfCoord.ETOG[i][j] * temp[j];
		}
	}
	
	// Return the Galactic coordinates as atnfAngles.
	var pg = atnfCoord.r2pol(gal[0], gal[1], gal[2]);
	var galAngles = {
		l: pg.left,
		b: pg.right
	};
	
	return galAngles;
};

/**
 * Convert an IAU 1958 Galactic position to the FK4 (B1950.0) system.
 * @param {atnfAngle} lat The Galactic latitude.
 * @param {atnfAngle} longi The Galactic longitude.
 */
atnfCoord.galfk4 = function(lat, longi) {
	// First convert our coordinates into the rectangular frame.
	var r = atnfCoord.pol2r(lat, longi);
	
	// Precess. Note that the same matrix is used as for the FK4 -> Galactic
  // transformation, but we have transposed it here.
  var fk4 = [];
	fk4[0] = atnfCoord.ETOG[0][0] * r.x +
		atnfCoord.ETOG[1][0] * r.y +
		atnfCoord.ETOG[2][0] * r.z;
	fk4[1] = atnfCoord.ETOG[0][1] * r.x +
		atnfCoord.ETOG[1][1] * r.y +
		atnfCoord.ETOG[2][1] * r.z;
	fk4[2] = atnfCoord.ETOG[0][2] * r.x +
		atnfCoord.ETOG[1][2] * r.y +
		atnfCoord.ETOG[2][2] * r.z;

	// Allow for e-terms.
	var w = r.x * atnfCoord.ETERM[0] +
		r.y * atnfCoord.ETERM[1] +
		r.z * atnfCoord.ETERM[2] + 1;
	for (var i = 0; i < 3; i++) {
		fk4[i] = (fk4[i] + atnfCoord.ETERM[i])/w;
	}
	
	// Return the coordinates as atnfAngles.
	var pr = atnfCoord.r2pol(fk4[0], fk4[1], fk4[2]);
	var bAngles = {
		ra: pr.left,
		dec: pr.right
	};
	
	return bAngles;
};

/**
 * Calculate the "on-the-sky" distance between two polar coordinates.
 * @param {object} referencePoint Reference point.
 * @param {object} targetPoint    Target point.
 */
atnfCoord.angularDistance = function(referencePoint, targetPoint) {
	// Move to left and right coordinates.
	var refCo = atnfCoord.pointLeftRight(referencePoint);
	var tgtCo = atnfCoord.pointLeftRight(targetPoint);

	// Prepare for the calculation.
	var dLat = tgtCo.left.subtract(refCo.left).toRadians();
	var dLon = tgtCo.right.subtract(refCo.right).toRadians();
	var lat1 = refCo.left.toRadians();
	var lat2 = tgtCo.left.toRadians();
	
	// Do the calculations, using the 'haversine' formula.
	var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
		Math.sin(dLon / 2) * Math.sin(dLon / 2) *
		Math.cos(lat1) * Math.cos(lat2);
	var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
	
	// Return this distance as an atnfAngle.
	var rd = atnfAngle({
		value: c,
		units: 'radians'
	});
	
	return rd;
};

/**
 * Move any supported coordinate names to left/right names.
 * @param {object} point The point to standardise.
 */
atnfCoord.pointLeftRight = function(point) {
	var retCo = {};
	
	if (typeof point.rightAscension !== 'undefined' &&
			typeof point.declination !== 'undefined') {
		retCo.left = point.rightAscension;
		retCo.right = point.declination;
	} else if (typeof point.latitude !== 'undefined' &&
						 typeof point.longitude !== 'undefined') {
		retCo.left = point.latitude;
		retCo.right = point.longitude;
	}
	
	return retCo;
};

/**
 * Some useful Javascript routines, not really anything to do with 
 * astro things. These are primarily routines that Dojo doesn't have.
 */
var atnfUseful = {};

/**
 * This routine checks if a string contains the specified substring.
 * @param {string} needle The string to search for.
 * @param {string} haystack The string to search in.
 * @param {boolean} caseInsensitive Case-insensitive search.
 */
atnfUseful.stringContains = function(needle, haystack, caseInsensitive) {
	if (caseInsensitive === true) {
		needle = needle.toLowerCase();
		haystack = haystack.toLowerCase();
	}
	return haystack.indexOf(needle) !== -1;
};

/**
 * This routine takes a DOM element and turns it into a collapsible
 * container. It offers some methods to handle showing and hiding the
 * collapsible elements.
 * @param {string} topId The ID of the DOM node to use as the parent
 *                       of the collapsible container.
 * @param {string} eventId The ID of the DOM node to attach an 'onclick'
 *                         event handler to for managing the showing/hiding.
 * @param {string} hideClass The name of a CSS class to apply to each
 *                           collapsible node when collapsing it.
 * @param {string} collapseClass The name of a CSS class to use to indicate
 *                               a collapsible node.
 */
atnfUseful.collapsibleContainer = function(topId, eventId, hideClass,
																					 collapseClass) {
	// Check that the elements exist.
	if (dojo.byId(topId) === null) {
		return null;
	}
	if (dojo.byId(eventId) === null) {
		return null;
	}
	// Check for the CSS classes.
	hideClass = hideClass || 'hide';
	collapseClass = collapseClass || 'collapse';
	
	var that = {};

	/**
	 * A status indication of whether we are collapsed.
	 * @type {boolean}
	 */
	var collapsed = false;

	/**
	 * A list of callbacks to execute upon show/hide.
	 * @type {array}
	 */
	var actionCalls = [];

	/**
	 * Make a call to each callback function.
	 */
	var makeCallbacks = function() {
		for (var i = 0; i < actionCalls.length; i++) {
			actionCalls[i](that);
		}
	};

	/**
	 * Add or remove the hidden class to each of the collapsible nodes.
	 * @param {string} action One of either 'add' or 'remove'.
	 */
	var applyClass = function(action) {
		var allNodes = dojo.query('#' + topId + ' > .collapse');
		for (var i = 0; i < allNodes.length; i++) {
			if (action === 'add') {
				dojo.addClass(allNodes[i], hideClass);
			} else if (action === 'remove') {
				dojo.removeClass(allNodes[i], hideClass);
			}
		}
	};
	
	/**
	 * Show all the collapsible information.
	 */
	that.show = function() {
		if (collapsed === false) {
			return;
		}

		applyClass('remove');
		
		collapsed = false;
		
		makeCallbacks();
	};
	
	/**
	 * Hide all the collapsible information.
	 */
	that.hide = function() {
		if (collapsed === true) {
			return;
		}
		
		applyClass('add');

		collapsed = true;
		
		makeCallbacks();
	};

	/**
	 * Add a callback routine to our list.
	 * @param {function} cFn The callback function to call.
	 */
	that.addCallback = function(cFn) {
		if (dojo.isFunction(cFn) === false) {
			return that;
		}
		
		actionCalls.push(cFn);
		
		return that;
	};

	/**
	 * Return the ID of our top node.
	 */
	that.parent = function() {
		return topId;
	};

	/**
	 * Return the ID of the event node.
	 */
	that.eventElement = function() {
		return eventId;
	};
	
	/**
	 * Return the collapsed state.
	 */
	that.state = function() {
		return collapsed;
	};
	
	// Attach our event handler.
	dojo.connect(dojo.byId(eventId), 'onclick', function(evtObj) {
		if (collapsed === true) {
			that.show();
		} else {
			that.hide();
		}
	});
	
	return that;
};

/**
 * This routine fires a callback only when the user presses Enter
 * (or the numeric keypad Enter) on some element with a passed ID.
 * @param {String} elementId The ID of the element to watch.
 * @param {Function} callback The function to call when Enter is pressed.
 */
atnfUseful.onEnter = function(elementId, callback) {
	// Check that the element exists.
	if (dojo.byId(elementId) === null) {
		return;
	}
	
	// Check that the callback function exists.
	if (dojo.isFunction(callback) === false) {
		return;
	}
	
	var handleKeys = function(evtObj) {
		// Check for the Enter keys.
		if (evtObj.keyCode === dojo.keys.ENTER ||
				evtObj.keyCode === dojo.keys.NUMPAD_ENTER) {
			// Fire the callback.
			callback(evtObj);
		}
	};
	
	// Connect the onkeydown event to the element.
	dojo.connect(dojo.byId(elementId), 'onkeydown', handleKeys);
	
	return;
};

/**
 * This routine makes a function to generate an animated status
 * message that cycles a number of dots at the end.
 * @param {String} message The message to display.
 * @param {String} elementId The ID of the element to display the
 *                           messsage in.
 */
atnfUseful.generateStatusMessage = function(message, elementId) {
	var that = {};
	
	/**
	 * Start the message display.
	 */
	that.start = function() {
		// Reset the number of dots.
		that.ndots = 1;
		
		// Display the initial message.
		that.display();
		
		// Start the timer.
		that.timer.start();
	};
	
	/**
	 * Update the number of dots.
	 */
	that.update = function() {
		// Increase the number of dots.
		that.ndots = (that.ndots === 3) ? 1 : ++that.ndots;

		// Display the message.
		that.display();
	};
	
	/**
	 * Display the message.
	 */
	that.display = function() {
		var dMessage = message;
		for (var i = 0; i < that.ndots; i++) {
			dMessage += '.';
		}
		
		dojo.attr(elementId, 'innerHTML', dMessage);
	};
	
	/**
	 * Stop the message display.
	 */
	that.stop = function() {
		// Stop the timer.
		that.timer.stop();

		// Empty the element.
		dojo.attr(elementId, 'innerHTML', '');
	};
	
	/**
	 * Our timer.
	 * @type {Timer}
	 */
	that.timer = new dojox.timing.Timer();
	that.timer.setInterval(250);
	that.timer.onTick = that.update;
	
	return that;
};

/**
 * The following blocks of code extend the native Javascript types with some
 * methods that will be useful to astronomers.
 */

/**
 * Convert a number in degrees into a number in turns.
 */
Number.prototype.degrees2turns = function() {
	return (this / 360.0);
};

/**
 * Convert a number in turns into a number in degrees.
 */
Number.prototype.turns2degrees = function() {
	return (this * 360.0);
};

/**
 * Convert a number in turns into a number in radians.
 */
Number.prototype.turns2radians = function() {
	return (this * 2 * Math.PI);
};

/**
 * Convert a number in radians into a number in turns.
 */
Number.prototype.radians2turns = function() {
	return (this / (2 * Math.PI));
};

/**
 * Convert a number in arcminutes into a number in degrees.
 */
Number.prototype.arcmin2degrees = function() {
	return (this / 60.0);
};

/**
 * Convert a number in degrees into a number in arcminutes.
 */
Number.prototype.degrees2arcmin = function() {
	return (this * 60.0);
};

/**
 * Convert a number in arcseconds into a number in degrees.
 */
Number.prototype.arcsec2degrees = function() {
	return (this / 3600.0);
};

/**
 * Convert a number in degrees into a number in arcseconds.
 */
Number.prototype.degrees2arcsec = function() {
	return (this * 3600.0);
};

/**
 * Convert a number in hours into a number in degrees.
 */
Number.prototype.hours2degrees = function() {
	return (this * 15.0);
};

/**
 * Convert a number in degrees into a number in hours.
 */
Number.prototype.degrees2hours = function() {
	return (this / 15.0);
};

/**
 * Convert a number in turns into its string representation.
 * @param {object} options An object containing conversion information.
 */
Number.prototype.turns2hexa = function(options) {
	// Default options.
  options = options || {};
	
	if (typeof options.units === 'undefined') {
		options.units = 'degrees';
	}
	if (typeof options.delimiter === 'undefined') {
		options.delimiter = ':';
	}
	if (typeof options.precision === 'undefined') {
		options.precision = 3;
	};
	if (typeof options.alwaysSigned === 'undefined') {
		options.alwaysSigned = false;
	};
	
	// Check that the number is between -1 and 1, as turns should be.
	if (this < -1 || this > 1) {
		return this;
	}
	
	// Convert the number into degrees.
	var n = this.turns2degrees();
	
	// Do we need to convert it to hours?
	if (typeof options.units !== 'undefined' &&
			options.units === 'hours') {
		n = n.degrees2hours();
	}
	
	// We'll deal only with positive numbers and add the sign later.
	var sign = 1;
	if (n < 0) {
		sign = -1;
	}
	n *= sign;
	
	var fields = [];
	fields[0] = Math.floor(n);
	n -= fields[0];
	n = n.degrees2arcmin();
	fields[1] = Math.floor(n);
	n -= fields[1];
	n = n.arcmin2degrees().degrees2arcsec();
	// Trim the last part to the appropriate precision.
	fields[2] = dojo.number.round(n, options.precision);
	
	// Turn it into a string.
	var output = '';
	for (var i = 0; i < fields.length; i++) {
		fields[i] = fields[i].zeroPad(10);
		if (i > 0) {
			output += options.delimiter;
		}
		output += fields[i];
	}

	// Put the sign back.
	if (sign < 0) {
		output = '-' + output;
	} else if (options.alwaysSigned === true) {
		output = '+' + output;
	}
	
	return output;
};

/**
 * Convert a number into a string with leading zeroes if required.
 * @param {number} bound The largest number (+1) to pad with leading zeroes.
 */
Number.prototype.zeroPad = function(bound) {
	var b = bound || 1;
	var o = '';
	var n = this;
	
	var sign = 1;
	if (n < 0) {
		sign = -1;
		n *= sign;
	}
	
	while (n < b && b >= 10) {
		o += '0';
		b /= 10;
	}

	if (sign === -1) {
		o = '-' + o;
	}
	
	return o + n;
};

/**
 * Add a plus sign to the front of a string if it doesn't already have
 * a minus sign.
 */
String.prototype.leadingPlus = function() {
	if (/^\-/.test(this) === true) {
		return this;
	}
	
	return '+' + this;
};

/**
 * Add zeroes to the end of a string to make it a specified length.
 * @param {number} reqLength The required string length.
 */
String.prototype.zeroTrail = function(reqLength) {
	var s = this;
	
	// Check that the number has a decimal point, and add one if it doesn't.
	if (/\./.test(s) === false) {
		s += '.';
	}

	// Add the trailing zeroes.
	while (s.length < reqLength) {
		s += '0';
	}
	
	return s;
};

/**
 * Convert a string formatted as "DD:DD:DD.D" (or close to this)
 * into a decimal number representing the number of turns.
 * @param {object} options An object containing conversion information.
 */
String.prototype.hexa2turns = function(options) {
	// Default options.
	options = options || {};
	if (typeof options.units === 'undefined') {
		options.units = 'degrees';
	};

	// Check for the sign of the number.
	var sign = 1;
	// Remove any plus sign at the start of the string.
	var h = this.replace(/^\+/, '');
  // Remove some things that may sometimes be present in angle strings.
  h = h.replace(/[\?\u00B0\']/g, ':');
  h = h.replace(/\"/g, '');
	if (/^\-/.test(h)) {
		// The string appears to be a negative number.
		sign = -1;
	}
	if (sign === -1) {
		// We need to get rid of the negative sign at the front of the string.
		h = h.replace(/^\-/, '');
	}

	// Break it into the component elements.
	if (/^\d+h\d+m[\d\.]+s*$/.test(h) === true) {
		// The string is in hours.
		options.units = 'hours';
		h = h.replace(/[hm]/g, ' ');
		h = h.replace(/s/, '');
	} else if (/^\d+d\d+m[\d\.]+s*$/.test(h) === true) {
		// The string is in degrees.
		options.units = 'degrees';
		h = h.replace(/[dm]/g, ' ');
		h = h.replace(/s/, '');
	}
	var hexaSplit = h.split(/[\s\:]/g);
		
	// Check that we only get 3 elements.
	if (hexaSplit.length !== 3) {
		// The string doesn't look right.
		return this;
	}
	// And that each element consists only of numerals.
	for (var i = 0; i < hexaSplit.length; i++) {
		if (/^[\d\.]+$/.test(hexaSplit[i]) === false) {
			// A non-numeric value showed up.
			return this;
		}
	}
		
	// Replace any leading zeroes in the split components.
	for (i = 0; i < hexaSplit.length; i++) {
		if (hexaSplit[i] !== '0') {
			hexaSplit[i] = hexaSplit[i].replace(/^0/, '');
		}
	}
		
	// Convert it into decimal now.
	var decimal = parseInt(hexaSplit[0]) +
		parseInt(hexaSplit[1]).arcmin2degrees() +
		parseFloat(hexaSplit[2]).arcsec2degrees();
		
	// Correct the sign now.
	decimal *= sign;
		
	// Apply any options.
	if (typeof options.units !== 'undefined' &&
			options.units === 'hours') {
		// The string was representing hours.
		decimal = decimal.hours2degrees();
	}
		
	// Convert it into turns now.
	return decimal.degrees2turns();
};

/**
 * Check if a string could be a minimum match for another specified string.
 * @param {String} longString The string to check against.
 */
String.prototype.minimumMatch = function(longString) {
	// Check that longString really is a string.
	if (dojo.isString(longString) === false) {
		return false;
	}

	// Check that the length of this string is not longer than
	// the longString.
	if (this.length > longString.length) {
		return false;
	}
	
	// Extract the right number of characters from the longString.
	var e = longString.substr(0, this.length);
	
	if (e == this) {
		return true;
	} else {
		return false;
	}
};

/**
 * This function makes it easy to take an astronomical source name and
 * get its source coordinates using some online services.
 * @param {object} spec Specifications for the object setup.
 * @param {object} my   An object reference that this object should be able
 *                      to access.
 */
var blankTemplate = function(spec, my) {
	/**
	 * The object we return to our callers.
	 * @type {object}
	 */
	var that = {};
	
	// Set some sensible defaults in the spec object.
	spec = spec || {};
	
	// Our methods follow.

	// Our private methods.

	// Our public methods.

	return that;
};
